reids


进入redis安装目录
cd /usr/local/src/redis-6.2.6
启动
redis-server redis.conf

一.数据结构

在这里插入图片描述

1.String

在这里插入图片描述

2.Hash

在这里插入图片描述

在这里插入图片描述

3.List

在这里插入图片描述

4.Set

在这里插入图片描述

5.SortedSet

在这里插入图片描述

二.Jedis

1.基本使用

在这里插入图片描述
f

2.连接池

在这里插入图片描述

3.SpringDataRedis

在这里插入图片描述

3.1基本使用

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.2序列化

如果mvc没自带,需要导入jackson依赖
在这里插入图片描述

  • 序列化的类型
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

3.3序列化方式二:StringRedisTemplate

在这里插入图片描述

在这里插入图片描述

三.短信登录

在这里插入图片描述

1.发送短信验证码

在这里插入图片描述

在这里插入图片描述

1.1.RegexUtils校验手机号的正则表达式

package com.hmdp.utils;


public abstract class RegexPatterns {
    /**
     * 手机号正则
     */
    public static final String PHONE_REGEX = "^1([38][0-9]|4[579]|5[0-3,5-9]|6[6]|7[0135678]|9[89])\\d{8}$";
    /**
     * 邮箱正则
     */
    public static final String EMAIL_REGEX = "^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$";
    /**
     * 密码正则。4~32位的字母、数字、下划线
     */
    public static final String PASSWORD_REGEX = "^\\w{4,32}$";
    /**
     * 验证码正则, 6位数字或字母
     */
    public static final String VERIFY_CODE_REGEX = "^[a-zA-Z\\d]{6}$";

}

1.2.实现

UserService

    @Override
    public Result sendCode(String phone, HttpSession session) {
        //1.校验手机号
        if (RegexUtils.isPhoneInvalid(phone)){
            return Result.fail("手机格式错误!");
        }
        //2.生成验证码
        String code = RandomUtil.randomNumbers(6);

        //3.保存验证码
        session.setAttribute("code",code);
        //4.短信发送验证码(未开发)
        log.debug(code);
        return Result.ok();
    }

UserController

    @PostMapping("code")
    public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {
        // TODO 发送短信验证码并保存验证码

        return userService.sendCode(phone,session);
    }

2.短信登录和注册

在这里插入图片描述
在这里插入图片描述
UserController

    @PostMapping("/login")
    public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){
        // TODO 实现登录功能
        return userService.login(loginForm,session);
    }

Service

@Override
    public Result login(LoginFormDTO loginForm, HttpSession session) {
        //1.校验手机号
        String phone = loginForm.getPhone();
        if (RegexUtils.isPhoneInvalid(phone)) {
            return Result.fail("手机号格式错误");
        }
        //2.校验验证码
        Object cacheCode = session.getAttribute("code");
        String code = loginForm.getCode();
        if (cacheCode==null || !cacheCode.toString().equals(code)) {
            return Result.fail("验证码错误");
        }
        //3.根据手机号查询用户
        User user = query().eq("phone", phone).one();
        //4.用户是否存在

        if (user == null){
            user = createUserWithPhone(phone);

        }
		//5.保存用户信息刀session
        session.setAttribute("User",user);
        return Result.ok();
    }

    private User createUserWithPhone(String phone) {
        User user = new User();
        user.setPhone(phone);
        user.setNickName(USER_NICK_NAME_PREFIX+ phone);
        save(user);
        return user;
    }

优雅常量写法

package com.hmdp.utils;

public class SystemConstants {
    public static final String IMAGE_UPLOAD_DIR = "D:\\lesson\\nginx-1.18.0\\html\\hmdp\\imgs\\";
    public static final String USER_NICK_NAME_PREFIX = "user_";
    public static final int DEFAULT_PAGE_SIZE = 5;
    public static final int MAX_PAGE_SIZE = 10;
}

3.登录校验拦截

在这里插入图片描述

3.1.拦截器

LoginInterceptor.java

package com.hmdp.utils;

import com.hmdp.dto.UserDTO;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //1.获取session
        HttpSession session = request.getSession();
        //2.获取session用户
        Object user = session.getAttribute("user");
        //3.判断用户是否存在
        if (user == null){
            //4.不存在拦截
            response.setStatus(401);
            return false;
        }

        //5.存在保存刀threadLocal
        UserHolder.saveUser((UserDTO) user);
        //6.放行

        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        UserHolder.removeUser();

    }
}

MvcConfig.java

package com.hmdp.config;

import com.hmdp.utils.LoginInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class MvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .excludePathPatterns(
                        "/user/code",
                        "/user/login",
                        "/blog/hot",
                        "/shop/**",
                        "/shop-type/**",
                        "/upload/**",
                        "/voucher/**"
                );
    }
}

3.2.封装ThredLocal工具类

package com.hmdp.utils;

import com.hmdp.dto.UserDTO;

public class UserHolder {
    private static final ThreadLocal<UserDTO> tl = new ThreadLocal<>();

    public static void saveUser(UserDTO user){
        tl.set(user);
    }

    public static UserDTO getUser(){
        return tl.get();
    }

    public static void removeUser(){
        tl.remove();
    }
}

4.Redis代替Session

在这里插入图片描述

4.1发送短信验证码

    @Override
    public Result sendCode(String phone, HttpSession session) {
        //1.校验手机号
        if (RegexUtils.isPhoneInvalid(phone)){
            return Result.fail("手机格式错误!");
        }
        //2.生成验证码
        String code = RandomUtil.randomNumbers(6);

        //3.保存验证码到redis
//        session.setAttribute("code",code);
        stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY+phone,code,LOGIN_CODE_TTL, TimeUnit.MINUTES);//key,value,过期时间,单位
        //4.短信发送验证码(未开发)
        log.debug("==============="+code+"===============");
        return Result.ok();
    }

4.2.短信验证和登录

在这里插入图片描述

package com.hmdp.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.dto.LoginFormDTO;
import com.hmdp.dto.Result;
import com.hmdp.dto.UserDTO;
import com.hmdp.entity.User;
import com.hmdp.mapper.UserMapper;
import com.hmdp.service.IUserService;
import com.hmdp.utils.RegexUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.http.HttpSession;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import static com.hmdp.utils.RedisConstants.*;
import static com.hmdp.utils.SystemConstants.USER_NICK_NAME_PREFIX;


@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
    @Resource
    private StringRedisTemplate stringRedisTemplate;
/*
* 发送验证码*/
    @Override
    public Result sendCode(String phone, HttpSession session) {
        //1.校验手机号
        if (RegexUtils.isPhoneInvalid(phone)){
            return Result.fail("手机格式错误!");
        }
        //2.生成验证码
        String code = RandomUtil.randomNumbers(6);

        //TODO 3.保存验证码到redis
//        session.setAttribute("code",code);
        stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY+phone,code,LOGIN_CODE_TTL, TimeUnit.MINUTES);//key,value,过期时间,单位
        //4.短信发送验证码(未开发)
        log.debug("==============="+code+"===============");
        return Result.ok();
    }
/*
* 登录*/
    @Override
    public Result login(LoginFormDTO loginForm, HttpSession session) {
        //1.校验手机号
        String phone = loginForm.getPhone();
        if (RegexUtils.isPhoneInvalid(phone)) {
            return Result.fail("手机号格式错误");
        }
        //TODO 2. 校验验证码
//        Object cacheCode = session.getAttribute("code");
        String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);

        String code = loginForm.getCode();
        if (cacheCode==null || !cacheCode.equals(code)) {
            return Result.fail("验证码错误");
        }
        //3.根据手机号查询用户
        User user = query().eq("phone", phone).one();
        //4.用户是否存在

        if (user == null){
            user = createUserWithPhone(phone);

        }
        //TODO 5.保存用户信息刀redis
        //5.1生成token
        String token = UUID.randomUUID().toString(true);

        //5.2将User转换为hash
        UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
        Map<String, Object> userMap = BeanUtil.beanToMap(userDTO,new HashMap<>(),
                new CopyOptions()
                        .setIgnoreNullValue(true)
                        .setFieldValueEditor((fieldName,fieldValue)->fieldValue.toString()));
                        //map的所以字段都转换成string,否则存redis会报错
        //5.4存储
        String tokenKey = LOGIN_USER_KEY+token;
        stringRedisTemplate.opsForHash().putAll(tokenKey,userMap);
        stringRedisTemplate.expire(tokenKey,LOGIN_USER_TTL,TimeUnit.MINUTES);//设置有效时间
        //5.2将user对象
        return Result.ok(token);
    }

/*
* 插入一条数据*/
    private User createUserWithPhone(String phone) {
        User user = new User();
        user.setPhone(phone);
        user.setNickName(USER_NICK_NAME_PREFIX+ phone);
        save(user);
        return user;
    }
}

4.3 拦截器

LoginInterceptor.java

package com.hmdp.utils;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.hmdp.dto.UserDTO;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
import java.util.concurrent.TimeUnit;



public class LoginInterceptor implements HandlerInterceptor {
    private StringRedisTemplate stringRedisTemplate;
//通过构造器注入stringRedisTemplate
    public LoginInterceptor(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //TODO 1.获取Token
        String token = request.getHeader("authorization");
        if (StrUtil.isBlank(token)) {
            response.setStatus(401);
            return false;
        }
        //TODO 2.获取Token用户
        String tokenKey = RedisConstants.LOGIN_USER_KEY + token;
        Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(tokenKey);
        //TODO 3.判断用户是否存在
        if (userMap.isEmpty()){
            //4.不存在拦截
            response.setStatus(401);
            return false;
        }
        //TODO 5.将map转user
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);

        //TODO 6.存在保存到threadLocal
        UserHolder.saveUser(userDTO);

        //TODO 7.刷新token有效期
        stringRedisTemplate.expire(tokenKey,RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);
        //TODO 6.放行

        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        UserHolder.removeUser();

    }
}

MvcConfig.java

package com.hmdp.config;

import com.hmdp.utils.LoginInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import javax.annotation.Resource;

@Configuration
public class MvcConfig implements WebMvcConfigurer {
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor(stringRedisTemplate))
                .excludePathPatterns(
                        "/user/code",
                        "/user/login",
                        "/blog/hot",
                        "/shop/**",
                        "/shop-type/**",
                        "/upload/**",
                        "/voucher/**"
                );
    }
}

四.商户查询缓存

1.添加商户缓存

在这里插入图片描述

Json转beanSONUtil.toBean(shopJson, Shop.class);
bean转Json JSONUtil.toJsonStr(shop);

Json转listJSONUtil.parseArray(shopTypeJson).toList(ShopType.class);
list转Json JSONUtil.toJsonStr(typeList);

//商户缓存

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public Result queryById(Long id) {
        String cacheShop = "cache:shop:";
        //1.从redis中查询
        String shopJson = stringRedisTemplate.opsForValue().get(cacheShop + id);
        //2.数据存在redis直接返回
        if (StrUtil.isNotBlank(shopJson)){
            //Json转bean
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
            return Result.ok(shop);
        }
        //3.不存在查询数据库
        Shop shop = getById(id);
        //判断数据库中是否存在
        if(shop == null){
            return Result.fail("店铺不存在");
        }
        //Bean转Json
        String shopJSon = JSONUtil.toJsonStr(shop);
        //4.向redis存值
        stringRedisTemplate.opsForValue().set(cacheShop+id,shopJSon);
        
        return Result.ok(shop);
    }

//商户类型缓存




@Service
public class ShopTypeServiceImpl extends ServiceImpl<ShopTypeMapper, ShopType> implements IShopTypeService {
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Override
    public Result queryList() {
        //1.查询redis是否有值
        String shopTypeJson = stringRedisTemplate.opsForValue().get(SHOP_TYPE_KEY);
        //不为空返回数据
        if (StrUtil.isNotBlank(shopTypeJson)){
            //Json转list
            List<ShopType> shopTypes = JSONUtil.parseArray(shopTypeJson).toList(ShopType.class);
            return Result.ok(shopTypes);
        }
        //2.查询数据库判断是否有值
        List<ShopType> typeList = query().orderByAsc("sort").list();
        //为空返回错误
        if (CollUtil.isEmpty(typeList)){
            return Result.fail("商品分类为空");
        }
        //list转Json
        String typeListJson = JSONUtil.toJsonStr(typeList);
        //3.redis中存入数据
        stringRedisTemplate.opsForValue().set(SHOP_TYPE_KEY,typeListJson);

        return Result.ok(typeList);
    }
}

2.缓存更新策略

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

    @Override
    public Result updateShopById(Shop shop) {
        //获取店铺id并判断是否为空
        Long id = shop.getId();
        if (id == null){
            return Result.fail("店铺不存在");
        }

        //更新店铺数据
        updateById(shop);
        //清除redis的数据
        stringRedisTemplate.delete(CACHE_SHOP_KEY + id);
        return Result.ok();
    }

3.缓存穿透

在这里插入图片描述
在这里插入图片描述

    @Override
    public Result queryById(Long id) {
        //1.从redis中查询
        String shopJson = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);
        //2.数据存在redis直接返回
        if (StrUtil.isNotBlank(shopJson)){

            //Json转bean
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);

            return Result.ok(shop);
        }
        //命中空值(防止缓存穿透)
        if (Objects.equals(shopJson, "")){
            return Result.fail("店铺不存在");
        }
        //3.不存在查询数据库
        Shop shop = getById(id);
        //判断数据库中是否存在
        if(shop == null){
            //缓存空值(防止缓存穿透)
            stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY+id,"",CACHE_NULL_TTL, TimeUnit.MINUTES);
            return Result.fail("店铺不存在");
        }
        //Bean转Json
        String shopJSon = JSONUtil.toJsonStr(shop);
        //4.向redis存值
        stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY+id,shopJSon,CACHE_SHOP_TTL,TimeUnit.MINUTES);

        return Result.ok(shop);
    }

4.缓存雪崩

在这里插入图片描述

5.缓存击穿

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5.1互斥锁

在这里插入图片描述

 @Resource
    private StringRedisTemplate stringRedisTemplate;


    @Override
    public Result queryById(Long id) {
        //return queryWithPassThrough(id);

        //1.从redis中查询
        String shopJson = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);
        //2.数据存在redis直接返回
        if (StrUtil.isNotBlank(shopJson)){
            //Json转bean
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);

            return Result.ok(shop);
        }
        //命中空值(防止缓存穿透)
        if (Objects.equals(shopJson, "")){
            return Result.fail("店铺不存在");
        }
        String lockKey = null;
        Shop shop = null;
        try {
            //尝试上锁
            lockKey = LOCK_SHOP_KEY + id;
            Boolean flag = tryLock(lockKey);
            if (!flag){
                Thread.sleep(50);
                queryById(id);
            }

            //3.不存在查询数据库
            shop = getById(id);
            //判断数据库中是否存在
            if(shop == null){
                //缓存控制(防止缓存穿透)
                stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY+ id,"",CACHE_NULL_TTL, TimeUnit.MINUTES);
                return Result.fail("店铺不存在");
            }
            //Bean转Json
            String shopJSon = JSONUtil.toJsonStr(shop);
            //4.向redis存值
            stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY+ id,shopJSon,CACHE_SHOP_TTL,TimeUnit.MINUTES);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            //释放锁
            delLock(lockKey);
        }

        return Result.ok(shop);
    }
    private Boolean tryLock(String key){
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", LOCK_SHOP_TTL, TimeUnit.MINUTES);
        return BooleanUtil.isTrue(flag);
    }
    private Boolean delLock(String key){
        Boolean delete = stringRedisTemplate.delete(key);
        return delete;
    }

5.2 逻辑删除

在这里插入图片描述

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    //创建线程池
    public static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);

    @Override
    public Result queryById(Long id) {
        //1.从redis中查询
        String shopJson = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);
        //2.数据存在redis直接返回
        if (StrUtil.isNotBlank(shopJson)){

            //Json转bean
            RedisData redisData = JSONUtil.toBean(shopJson, RedisData.class);
            Shop shop = JSONUtil.toBean((JSONObject) redisData.getData(), Shop.class);

            //对比时间,判断是否过期
            LocalDateTime  expireTime= redisData.getExpireTime();
            if (expireTime.isAfter(LocalDateTime.now())){
                return Result.ok(shop);
            }
            //上锁

                Boolean flag = tryLock(LOCK_SHOP_KEY + id);
                if (flag){
                    //提交一个线程
                    CACHE_REBUILD_EXECUTOR.submit(()->{
                        try {
                            Shop newShop = getById(id);
                            saveShopRedis(id,newShop);
                            System.out.println("===========================调用新的线程");
                        } catch (Exception e) {
                            throw new RuntimeException(e);
                        } finally {
                            delLock(LOCK_SHOP_KEY + id);
                            System.out.println("===================释放锁");
                        }
                    });
                }


            return Result.ok(shop);
        }
        //命中空值(缓存穿透)
        if (Objects.equals(shopJson, "")){
            return Result.fail("店铺不存在");
        }
        //3.不存在查询数据库
        Shop shop = getById(id);
        //判断数据库中是否存在
        if(shop == null){
            //缓存控制(缓存穿透)
            stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY+ id,"",CACHE_NULL_TTL, TimeUnit.MINUTES);
            return Result.fail("店铺不存在");
        }
        saveShopRedis(id,shop);

        return Result.ok(shop);
    }


    private void saveShopRedis(Long id,Shop shop) {
        //更新数据
        RedisData newRedisData = new RedisData();
        newRedisData.setData(shop);
        newRedisData.setExpireTime(LocalDateTime.now().plusSeconds(20));
        stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id,JSONUtil.toJsonStr(newRedisData));
    }

    private Boolean tryLock(String key){
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", LOCK_SHOP_TTL, TimeUnit.MINUTES);
        return BooleanUtil.isTrue(flag);
    }
    private Boolean delLock(String key){
        Boolean delete = stringRedisTemplate.delete(key);
        return delete;
    }

工具封装

在这里插入图片描述

package com.hmdp.utils;

import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

import static com.hmdp.utils.RedisConstants.CACHE_NULL_TTL;
import static com.hmdp.utils.RedisConstants.LOCK_SHOP_KEY;

@Slf4j
@Component
public class CacheClient {

    private final StringRedisTemplate stringRedisTemplate;

    private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);

    public CacheClient(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    public void set(String key, Object value, Long time, TimeUnit unit) {
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, unit);
    }

    public void setWithLogicalExpire(String key, Object value, Long time, TimeUnit unit) {
        // 设置逻辑过期
        RedisData redisData = new RedisData();
        redisData.setData(value);
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));
        // 写入Redis
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));
    }

    //解决缓存穿透
    public <R,ID> R queryWithPassThrough(
            String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit){
        String key = keyPrefix + id;
        // 1.从redis查询商铺缓存
        String json = stringRedisTemplate.opsForValue().get(key);
        // 2.判断是否存在
        if (StrUtil.isNotBlank(json)) {
            // 3.存在,直接返回
            return JSONUtil.toBean(json, type);
        }
        // 判断命中的是否是空值
        if (json != null) {
            // 返回一个错误信息
            return null;
        }

        // 4.不存在,根据id查询数据库
        R r = dbFallback.apply(id);
        // 5.不存在,返回错误
        if (r == null) {
            // 将空值写入redis
            stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
            // 返回错误信息
            return null;
        }
        // 6.存在,写入redis
        this.set(key, r, time, unit);
        return r;
    }
    //使用逻辑过期时间解决缓存击穿
    public <R, ID> R queryWithLogicalExpire(
            String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit) {
        String key = keyPrefix + id;
        // 1.从redis查询商铺缓存
        String json = stringRedisTemplate.opsForValue().get(key);
        // 2.判断是否存在
        if (StrUtil.isBlank(json)) {
            // 3.存在,直接返回
            return null;
        }
        // 4.命中,需要先把json反序列化为对象
        RedisData redisData = JSONUtil.toBean(json, RedisData.class);
        R r = JSONUtil.toBean((JSONObject) redisData.getData(), type);
        LocalDateTime expireTime = redisData.getExpireTime();
        // 5.判断是否过期
        if(expireTime.isAfter(LocalDateTime.now())) {
            // 5.1.未过期,直接返回店铺信息
            return r;
        }
        // 5.2.已过期,需要缓存重建
        // 6.缓存重建
        // 6.1.获取互斥锁
        String lockKey = LOCK_SHOP_KEY + id;
        boolean isLock = tryLock(lockKey);
        // 6.2.判断是否获取锁成功
        if (isLock){
            // 6.3.成功,开启独立线程,实现缓存重建
            CACHE_REBUILD_EXECUTOR.submit(() -> {
                try {
                    // 查询数据库
                    R newR = dbFallback.apply(id);
                    // 重建缓存
                    this.setWithLogicalExpire(key, newR, time, unit);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }finally {
                    // 释放锁
                    unlock(lockKey);
                }
            });
        }
        // 6.4.返回过期的商铺信息
        return r;
    }

    //使用互斥锁解决缓存击穿
    public <R, ID> R queryWithMutex(
            String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit) {
        String key = keyPrefix + id;
        // 1.从redis查询商铺缓存
        String shopJson = stringRedisTemplate.opsForValue().get(key);
        // 2.判断是否存在
        if (StrUtil.isNotBlank(shopJson)) {
            // 3.存在,直接返回
            return JSONUtil.toBean(shopJson, type);
        }
        // 判断命中的是否是空值
        if (shopJson != null) {
            // 返回一个错误信息
            return null;
        }

        // 4.实现缓存重建
        // 4.1.获取互斥锁
        String lockKey = LOCK_SHOP_KEY + id;
        R r = null;
        try {
            boolean isLock = tryLock(lockKey);
            // 4.2.判断是否获取成功
            if (!isLock) {
                // 4.3.获取锁失败,休眠并重试
                Thread.sleep(50);
                return queryWithMutex(keyPrefix, id, type, dbFallback, time, unit);
            }
            // 4.4.获取锁成功,根据id查询数据库
            r = dbFallback.apply(id);
            // 5.不存在,返回错误
            if (r == null) {
                // 将空值写入redis
                stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
                // 返回错误信息
                return null;
            }
            // 6.存在,写入redis
            this.set(key, r, time, unit);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }finally {
            // 7.释放锁
            unlock(lockKey);
        }
        // 8.返回
        return r;
    }

    private boolean tryLock(String key) {
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(flag);
    }

    private void unlock(String key) {
        stringRedisTemplate.delete(key);
    }
}

调用

    @Override
    public Result queryById(Long id) {

        Shop shop = cacheClient.queryWithPassThrough(CACHE_SHOP_KEY, id, Shop.class, this::getById, CACHE_SHOP_TTL, TimeUnit.MINUTES);//(前缀,id,类型,查询数据库的方法,时间,单位)
        if (shop == null){
            return Result.fail("店铺不存在");
        }
        return Result.ok(shop);
    }

五.优惠卷秒杀

1.全局唯一ID

在这里插入图片描述

package com.hmdp.utils;

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;

@Component
public class RedisIdWorker {
    /**
     * 开始时间戳
     */
    private static final long BEGIN_TIMESTAMP = 1640995200L;
    /**
     * 序列号的位数
     */
    private static final int COUNT_BITS = 32;

    private StringRedisTemplate stringRedisTemplate;

    public RedisIdWorker(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    public long nextId(String keyPrefix) {
        // 1.生成时间戳
        LocalDateTime now = LocalDateTime.now();
        long nowSecond = now.toEpochSecond(ZoneOffset.UTC);
        long timestamp = nowSecond - BEGIN_TIMESTAMP;

        // 2.生成序列号
        // 2.1.获取当前日期,精确到天
        String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
        // 2.2.自增长
        long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + date);

        // 3.拼接并返回
        return timestamp << COUNT_BITS | count;
    }
}

2.秒杀优惠卷

在这里插入图片描述

@Transactional
 @Override
    public Result seckillVoucher(Long voucherId) {
        //1.查询优惠卷
        SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
        if (voucher == null){
            return Result.fail("优惠卷不存在");
        }
        //2.判断秒杀是否开始
        LocalDateTime beginTime = voucher.getBeginTime();
        if (beginTime.isAfter(LocalDateTime.now())){
            return Result.fail("活动尚未开始");
        }
        //3.判断秒杀是否结束 begin<now<end
        LocalDateTime endTime = voucher.getEndTime();
        if (endTime.isBefore(LocalDateTime.now())){
            return Result.fail("活动已经结束");
        }
        //4.判断库存是否充足
        Integer stock = voucher.getStock();
        if (stock < 1){
            return Result.fail("库存不足");
        }
       //5.扣减库存
        LambdaUpdateWrapper<SeckillVoucher> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(SeckillVoucher::getVoucherId,voucherId);
        updateWrapper.set(SeckillVoucher::getStock,stock-1);
        boolean flag = seckillVoucherService.update(null,updateWrapper);


        if (!flag){
            return Result.fail("库存不足");
        }
        //6.创建订单
        VoucherOrder voucherOrder = new VoucherOrder();
        //订单id
        long orderId = redisIdWorker.nextId("order");
        voucherOrder.setId(orderId);
        //用户id
        Long userId = UserHolder.getUser().getId();
        voucherOrder.setUserId(userId);
        //代金卷id
        voucherOrder.setVoucherId(voucherId);
        save(voucherOrder);
        //7.返回订单
        return Result.ok(orderId);

    }

3.超卖问题

在这里插入图片描述

3.1 乐观锁

在这里插入图片描述

在这里插入图片描述

       LambdaUpdateWrapper<SeckillVoucher> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(SeckillVoucher::getVoucherId,voucherId);
        //乐观锁()
        updateWrapper.eq(SeckillVoucher::getStock,stock);
        updateWrapper.set(SeckillVoucher::getStock,stock-1);
        boolean flag = seckillVoucherService.update(null,updateWrapper);

以上方式解决了超卖问题但是成功率太低
解决

       //5.扣减库存
        LambdaUpdateWrapper<SeckillVoucher> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(SeckillVoucher::getVoucherId,voucherId);
        //乐观锁(改为gt)
          updateWrapper.gt(SeckillVoucher::getStock,0); //乐观锁
        updateWrapper.setSql("stock = stock-1");
        boolean flag = seckillVoucherService.update(null,updateWrapper);

3.2.悲观锁

在这里插入图片描述
pom.xml

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
        </dependency>

HmDianPingApplication添加@EnableAspectJAutoProxy

@EnableAspectJAutoProxy(exposeProxy = true)
@MapperScan("com.hmdp.mapper")
@SpringBootApplication
public class HmDianPingApplication {

    public static void main(String[] args) {
        SpringApplication.run(HmDianPingApplication.class, args);
    }

}

VoucherOrderServiceImpl.java

  @Resource
    private ISeckillVoucherService seckillVoucherService;

    @Resource
    private RedisIdWorker redisIdWorker;
    @Override
    public Result seckillVoucher(Long voucherId) {
        //1.查询优惠卷
        SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
        if (voucher == null){
            return Result.fail("优惠卷不存在");
        }
        //2.判断秒杀是否开始
        LocalDateTime beginTime = voucher.getBeginTime();
        if (beginTime.isAfter(LocalDateTime.now())){
            return Result.fail("活动尚未开始");
        }
        //3.判断秒杀是否结束 begin<now<end
        LocalDateTime endTime = voucher.getEndTime();
        if (endTime.isBefore(LocalDateTime.now())){
            return Result.fail("活动已经结束");
        }
        //4.判断库存是否充足
        Integer stock = voucher.getStock();
        if (stock < 1){
            return Result.fail("库存不足");
        }
        Long userId = UserHolder.getUser().getId();
        //悲观锁
        synchronized(userId.toString().intern()){//intern()从字符串池返回,防止不同对象带来的差异
            IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();//采用AopContext方式,解决Spring事务注解失效
            return proxy.createVoucherOrder(voucherId);
        }


    }

    @Transactional
    public Result createVoucherOrder(Long voucherId) {
        //一人一单
        Long userId = UserHolder.getUser().getId();
        int count = lambdaQuery().eq(VoucherOrder::getVoucherId, voucherId).eq(VoucherOrder::getUserId, userId).count();
        if (count>0){
            return Result.fail("该优惠价不可重复抢购");
        }
        //5.扣减库存
        LambdaUpdateWrapper<SeckillVoucher> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(SeckillVoucher::getVoucherId, voucherId);
        updateWrapper.gt(SeckillVoucher::getStock,0); //乐观锁(优惠卷超卖)
        //updateWrapper.set(SeckillVoucher::getStock,stock-1);
        updateWrapper.setSql("stock = stock-1");
        boolean flag = seckillVoucherService.update(null,updateWrapper);


        if (!flag){
            return Result.fail("库存不足");
        }
        //6.创建订单
        VoucherOrder voucherOrder = new VoucherOrder();
        //订单id
        long orderId = redisIdWorker.nextId("order");
        voucherOrder.setId(orderId);
        //用户id

        voucherOrder.setUserId(userId);
        //代金卷id
        voucherOrder.setVoucherId(voucherId);
        save(voucherOrder);
        //7.返回订单
        return Result.ok(orderId);
    }

3.3并发安全问题(悲观锁失效)

在这里插入图片描述

如果有多台服务器,每台都有自己的锁对象,导致并发访问有多把锁
解决:使用分布式锁

六.分布式锁

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.解决并发安全问题

        //分布式锁
        //synchronized(userId.toString().intern())
        SimpleRedisLock lock = new SimpleRedisLock("order:" + userId, stringRedisTemplate);
        boolean isLock = lock.tryLock(60);
        if (!isLock){
            return Result.fail("该优惠价不可重复抢购");
        }

        try {
            IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
            return proxy.createVoucherOrder(voucherId);
        } finally {
            lock.unlock();
        }

ILock.interface

public interface ILock {

    /**
     * 尝试获取锁
     * @param timeoutSec 锁持有的超时时间,过期后自动释放
     * @return true代表获取锁成功; false代表获取锁失败
     */
    boolean tryLock(long timeoutSec);

    /**
     * 释放锁
     */
    void unlock();
}

SimpleRedisLock.java

public class SimpleRedisLock implements ILock {

    private String name;
    private StringRedisTemplate stringRedisTemplate;

    public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {
        this.name = name;
        this.stringRedisTemplate = stringRedisTemplate;
    }

    private static final String KEY_PREFIX = "lock:";
    private static final String ID_PREFIX = UUID.randomUUID().toString(true) + "-";


    @Override
    public boolean tryLock(long timeoutSec) {
        // 获取线程标示
        String threadId = ID_PREFIX + Thread.currentThread().getId();
        // 获取锁
        Boolean success = stringRedisTemplate.opsForValue()
                .setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(success);
    }


    @Override
    public void unlock() {
        // 获取线程标示
        String threadId = ID_PREFIX + Thread.currentThread().getId();
        // 获取锁中的标示
        String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name);
        // 判断标示是否一致
        if(threadId.equals(id)) {
            // 释放锁
            stringRedisTemplate.delete(KEY_PREFIX + name);
        }
    }
}

2.分布式锁的原子性问题

在这里插入图片描述

线程1判断标示一致后,jvm堵塞导致超时释放锁,线程2开始执行。线程1堵塞结束执行手动释放锁时,误删线程2的锁,进而导致线程3获取到锁.

在这里插入图片描述

    private static final DefaultRedisScript<Long> UNLOCK_SCRIPT;
    static {
        UNLOCK_SCRIPT = new DefaultRedisScript<>();
        UNLOCK_SCRIPT.setLocation(new ClassPathResource("unlock.lua"));
        UNLOCK_SCRIPT.setResultType(Long.class);
    }
        @Override
    public void unlock() {
        // 调用lua脚本
        stringRedisTemplate.execute(
                UNLOCK_SCRIPT,
                Collections.singletonList(KEY_PREFIX + name),
                ID_PREFIX + Thread.currentThread().getId());
    }

unlock.lua

-- 比较线程标示与锁中的标示是否一致
if(redis.call('get', KEYS[1]) ==  ARGV[1]) then
    -- 释放锁 del key
    return redis.call('del', KEYS[1])
end
return 0

3.Redission

在这里插入图片描述

3.1配置

在这里插入图片描述

        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.13.6</version>
        </dependency>

RedissonConfig.java

@Configuration
public class RedissonConfig {

    @Bean
    public RedissonClient redissonClient(){
        // 配置
        Config config = new Config();
        config.useSingleServer().setAddress("redis://192.168.150.101:6379").setPassword("123321");
        // 创建RedissonClient对象
        return Redisson.create(config);
    }
}

3.2入门使用

在这里插入图片描述

    @Resource
    private RedissonClient redissonClient;
     @Override
    public Result seckillVoucher(Long voucherId) {
  ...省略代码
        //分布式锁
//        SimpleRedisLock lock = new SimpleRedisLock("order:" + userId, stringRedisTemplate);
//        boolean isLock = lock.tryLock(60);


        RLock lock = redissonClient.getLock("lock:order:" + userId);//指定锁名称
        boolean isLock = lock.tryLock();//不指定参数则不会重入
        if (!isLock){
            return Result.fail("该优惠价不可重复抢购");
        }

        try {
            IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
            return proxy.createVoucherOrder(voucherId);
        } finally {
            lock.unlock();
        }


    }

3.3 multiLock

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4.异步

4.1.阻塞队列实现异步秒杀

在这里插入图片描述
在这里插入图片描述

seckill.lua

-- 1.参数列表
-- 1.1.优惠券id
local voucherId = ARGV[1]
-- 1.2.用户id
local userId = ARGV[2]


-- 2.数据key
-- 2.1.库存key
local stockKey = 'seckill:stock:' .. voucherId
-- 2.2.订单key
local orderKey = 'seckill:order:' .. voucherId

-- 3.脚本业务
-- 3.1.判断库存是否充足 get stockKey
if(tonumber(redis.call('get', stockKey)) <= 0) then
    -- 3.2.库存不足,返回1
    return 1
end
-- 3.2.判断用户是否下单 SISMEMBER orderKey userId
if(redis.call('sismember', orderKey, userId) == 1) then
    -- 3.3.存在,说明是重复下单,返回2
    return 2
end
-- 3.4.扣库存 incrby stockKey -1
redis.call('incrby', stockKey, -1)
-- 3.5.下单(保存用户)sadd orderKey userId
redis.call('sadd', orderKey, userId)

return 0

VoucherOrderServiceImpl.java

1.初始化lua脚本 —> 执行lua脚本
2.初始化阻塞队列 —> 添加订单信息到阻塞队列
3.创建线程池 —>在独立线程中获取队列信信息and创建订单

@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {

    @Resource
    private ISeckillVoucherService seckillVoucherService;
    @Resource
    private RedisIdWorker redisIdWorker;
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private RedissonClient redissonClient;
    //获取LUA脚本
    private static final DefaultRedisScript<Long> SECKILL_SCRIPT;
    //初始化阻塞队列
    private BlockingQueue<VoucherOrder> orderTasks= new ArrayBlockingQueue<VoucherOrder>(1024*1024);
    //创建线程池
    private static final ExecutorService SECKILL_ORDER_EXECUTOR = Executors.newSingleThreadExecutor();

    @PostConstruct
    private void init(){
        SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandler());
    }
    private class VoucherOrderHandler implements Runnable {
        @Override
        public void run() {
            while (true) {
                try {
                    //获取队列中的信息
                    VoucherOrder voucherOrder = orderTasks.take();
                    //创建订单
                    handleVoucherOrder(voucherOrder);
                } catch (Exception e) {
                    log.error("订单处理异常",e);
                }
            }
        }
    }
    private IVoucherOrderService proxy;
    private void handleVoucherOrder(VoucherOrder voucherOrder) {
       /*如果此时调用方没有添加事务注解@Transactional,
       而在被调用方添加事务注解@Transactional,
       当被调用方法中出现异常,
       这时候会发现事务并没有回滚,
       事务注解@Transactional没有起作用*/
        proxy.createVoucherOrder(voucherOrder);
    }


    static {
        SECKILL_SCRIPT = new DefaultRedisScript<>();
        SECKILL_SCRIPT.setLocation(new ClassPathResource("seckill.lua"));
        SECKILL_SCRIPT.setResultType(Long.class);
    }

    @Override
    public Result seckillVoucher(Long voucherId) {


        Long userId = UserHolder.getUser().getId();
        //1.执行LUA脚本
        Long result = stringRedisTemplate.execute(SECKILL_SCRIPT, Collections.emptyList(), voucherId.toString(), userId.toString());
        int r = result.intValue();
        if (r != 0){
            return Result.fail(r==1?"库存不足":"该优惠价不可重复抢购");
        }
        //生成订单号
        long orderId = redisIdWorker.nextId("order");
        //添加订单到阻塞队,等待添加到数据库
        VoucherOrder voucherOrder = new VoucherOrder();
        voucherOrder.setId(orderId);
        voucherOrder.setVoucherId(voucherId);
        voucherOrder.setUserId(userId);
        orderTasks.add(voucherOrder);
        //初始化代理
        proxy = (IVoucherOrderService) AopContext.currentProxy();
        //处理订单
        return Result.ok(orderId);
    }


    @Transactional
    @Override
    public void createVoucherOrder(VoucherOrder voucherOrder) {
       // 扣减库存
        LambdaUpdateWrapper<SeckillVoucher> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(SeckillVoucher::getVoucherId, voucherOrder.getVoucherId());
        updateWrapper.gt(SeckillVoucher::getStock,0); //乐观锁
        //updateWrapper.set(SeckillVoucher::getStock,stock-1);
        updateWrapper.setSql("stock = stock-1");
        boolean flag = seckillVoucherService.update(null,updateWrapper);
        if (!flag){
            log.error("库存不足");
        }
        save(voucherOrder);
    }
}

4.2.消息队列实现异步秒杀

跳转到消息队列实现异步秒杀

5.redis实现消息队列

在这里插入图片描述

在这里插入图片描述

5.1.List模拟消息队列

在这里插入图片描述
在这里插入图片描述

5.2.pubsub

在这里插入图片描述

在这里插入图片描述

5.3.stream

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

5.4.stream — 消费者组

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5.5.消息队列实现异步秒杀

创建队列和组
在这里插入图片描述
VoucherOrderServiceImpl.java

@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {

    @Resource
    private ISeckillVoucherService seckillVoucherService;
    @Resource
    private RedisIdWorker redisIdWorker;
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private RedissonClient redissonClient;
    //获取LUA脚本
    private static final DefaultRedisScript<Long> SECKILL_SCRIPT;

    //创建线程池
    private static final ExecutorService SECKILL_ORDER_EXECUTOR = Executors.newSingleThreadExecutor();

    @PostConstruct
    private void init(){
        SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandler());
    }
    private class VoucherOrderHandler implements Runnable {
        @Override
        public void run() {
            while (true) {
                try {
                    // 1.获取消息队列中的订单信息 XREADGROUP GROUP g1 c1 COUNT 1 BLOCK 2000 STREAMS s1 >
                    List<MapRecord<String, Object, Object>> list = stringRedisTemplate.opsForStream().read(
                            Consumer.from("g1", "c1"),
                            StreamReadOptions.empty().count(1).block(Duration.ofSeconds(2)),
                            StreamOffset.create("stream.orders", ReadOffset.lastConsumed())
                    );
                    // 2.判断订单信息是否为空
                    if (list == null || list.isEmpty()) {
                        // 如果为null,说明没有消息,继续下一次循环
                        continue;
                    }
                    // 解析数据
                    MapRecord<String, Object, Object> record = list.get(0);
                    Map<Object, Object> value = record.getValue();
                    VoucherOrder voucherOrder = BeanUtil.fillBeanWithMap(value, new VoucherOrder(), true);
                    // 3.创建订单
                    createVoucherOrder(voucherOrder);
                    // 4.确认消息 XACK
                    stringRedisTemplate.opsForStream().acknowledge("s1", "g1", record.getId());
                } catch (Exception e) {
                    log.error("订单处理异常",e);
                    handlePendingList();
                }
            }
        }

        private void handlePendingList() {
            while (true) {
                try {
                    // 1.获取pending-list中的订单信息 XREADGROUP GROUP g1 c1 COUNT 1 BLOCK 2000 STREAMS s1 0
                    List<MapRecord<String, Object, Object>> list = stringRedisTemplate.opsForStream().read(
                            Consumer.from("g1", "c1"),
                            StreamReadOptions.empty().count(1),
                            StreamOffset.create("stream.orders", ReadOffset.from("0"))
                    );
                    // 2.判断订单信息是否为空
                    if (list == null || list.isEmpty()) {
                        // 如果为null,说明没有异常消息,结束循环
                        break;
                    }
                    // 解析数据
                    MapRecord<String, Object, Object> record = list.get(0);
                    Map<Object, Object> value = record.getValue();
                    VoucherOrder voucherOrder = BeanUtil.fillBeanWithMap(value, new VoucherOrder(), true);
                    // 3.创建订单
                    createVoucherOrder(voucherOrder);
                    // 4.确认消息 XACK
                    stringRedisTemplate.opsForStream().acknowledge("s1", "g1", record.getId());
                } catch (Exception e) {
                    log.error("处理订单异常", e);
                }
            }
        }
    }
    private IVoucherOrderService proxy;
    private void handleVoucherOrder(VoucherOrder voucherOrder) {
       //如果此时调用方没有添加事务注解@Transactional,而在被调用方添加事务注解@Transactional,当被调用方法中出现异常,这时候会发现事务并没有回滚,事务注解@Transactional没有起作用
        proxy.createVoucherOrder(voucherOrder);
    }


    static {
        SECKILL_SCRIPT = new DefaultRedisScript<>();
        SECKILL_SCRIPT.setLocation(new ClassPathResource("seckill.lua"));
        SECKILL_SCRIPT.setResultType(Long.class);
    }

    @Override
    public Result seckillVoucher(Long voucherId) {


        Long userId = UserHolder.getUser().getId();
        //生成订单号
        long orderId = redisIdWorker.nextId("order");
        //1.执行LUA脚本
        Long result = stringRedisTemplate.execute(SECKILL_SCRIPT, Collections.emptyList(), voucherId.toString(), userId.toString(),String.valueOf(orderId));
        int r = result.intValue();
        if (r != 0){
            return Result.fail(r==1?"库存不足":"该优惠价不可重复抢购");
        }



        //初始化代理
        proxy = (IVoucherOrderService) AopContext.currentProxy();
        //处理订单
        return Result.ok(orderId);
    }


    @Transactional
    @Override
    public void createVoucherOrder(VoucherOrder voucherOrder) {
       // 扣减库存
        LambdaUpdateWrapper<SeckillVoucher> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(SeckillVoucher::getVoucherId, voucherOrder.getVoucherId());
        updateWrapper.gt(SeckillVoucher::getStock,0); //乐观锁
        //updateWrapper.set(SeckillVoucher::getStock,stock-1);
        updateWrapper.setSql("stock = stock-1");
        boolean flag = seckillVoucherService.update(null,updateWrapper);
        if (!flag){
            log.error("库存不足");
        }
        save(voucherOrder);
    }



}

seckill.lua

-- 1.参数列表
-- 1.1.优惠券id
local voucherId = ARGV[1]
-- 1.2.用户id
local userId = ARGV[2]
-- 1.3.订单id
local orderId = ARGV[3]

-- 2.数据key
-- 2.1.库存key
local stockKey = 'seckill:stock:' .. voucherId
-- 2.2.订单key
local orderKey = 'seckill:order:' .. voucherId

-- 3.脚本业务
-- 3.1.判断库存是否充足 get stockKey
if(tonumber(redis.call('get', stockKey)) <= 0) then
    -- 3.2.库存不足,返回1
    return 1
end
-- 3.2.判断用户是否下单 SISMEMBER orderKey userId
if(redis.call('sismember', orderKey, userId) == 1) then
    -- 3.3.存在,说明是重复下单,返回2
    return 2
end
-- 3.4.扣库存 incrby stockKey -1
redis.call('incrby', stockKey, -1)
-- 3.5.下单(保存用户)sadd orderKey userId
redis.call('sadd', orderKey, userId)
-- 3.6.发送消息到队列中, XADD stream.orders * k1 v1 k2 v2 ...
redis.call('xadd', 'stream.orders', '*', 'userId', userId, 'voucherId', voucherId, 'id', orderId)
return 0

七.


  • blogController.java
@RestController
@RequestMapping("/blog")
public class BlogController {

    @Resource
    private IBlogService blogService;
    @Resource
    private IUserService userService;

    @PostMapping
    public Result saveBlog(@RequestBody Blog blog) {
        return blogService.saveBlog(blog);
    }

    @PutMapping("/like/{id}")
    public Result likeBlog(@PathVariable("id") Long id) {
        // 修改点赞数量
//        blogService.update()
//                .setSql("liked = liked + 1").eq("id", id).update();
        return blogService.likeBlog(id);
    }

    @GetMapping("/of/me")
    public Result queryMyBlog(@RequestParam(value = "current", defaultValue = "1") Integer current) {
        // 获取登录用户
        UserDTO user = UserHolder.getUser();
        // 根据用户查询
        Page<Blog> page = blogService.query()
                .eq("user_id", user.getId()).page(new Page<>(current, SystemConstants.MAX_PAGE_SIZE));
        // 获取当前页数据
        List<Blog> records = page.getRecords();
        return Result.ok(records);
    }
    @GetMapping("/of/user")
    public Result getUserBlog(@RequestParam("id") Long id,@RequestParam("current") Long current ){
        return blogService.getUserBlog(id,current);
    }
    @GetMapping("of/follow")
    public Result getFollowBlog(@RequestParam("lastId") Long lastID ,@RequestParam(value = "offset",defaultValue = "0") Integer offset){
        return blogService.getFollowBlog(lastID,offset);
    }

    @GetMapping("/hot")
    public Result queryHotBlog(@RequestParam(value = "current", defaultValue = "1") Integer current) {
        return blogService.queryHotBlog(current);
    }
    @GetMapping("/{id}")
    public Result queryBlogById(@PathVariable("id") Long id){
        return blogService.queryBlogById(id);
    }
    @GetMapping("/likes/{id}")
    public Result queryBlogLikesById(@PathVariable("id") Long id){
        return blogService.queryBlogLikesById(id);
    }
}

bolgServiceImpl.java

@Service
public class BlogServiceImpl extends ServiceImpl<BlogMapper, Blog> implements IBlogService {

    @Resource
    private IUserService userService;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private IFollowService followService;

    @Override
    public Result queryHotBlog(Integer current) {
        // 根据用户查询
        Page<Blog> page = query()
                .orderByDesc("liked")
                .page(new Page<>(current, SystemConstants.MAX_PAGE_SIZE));
        // 获取当前页数据
        List<Blog> records = page.getRecords();


        // 查询用户

        records.forEach(blog -> {
            this.blogSetUser(blog);
            this.isBlogLiked(blog);
        });


        return Result.ok(records);
    }

    @Override
    public Result queryBlogLikesById(Long id) {
        //查询钱五个点赞的
        String blog_Liked_Key = BLOG_LIKED_KEY + id;
        Set<String> range = stringRedisTemplate.opsForZSet().range(blog_Liked_Key, 0, 4);
        //判断是否有人点赞
        if (range == null || range.isEmpty()){
            return Result.ok(Collections.emptyList());
        }
        //Set<String>转 List<Long>
        List<Long> top5 = range.stream().map(s -> Long.valueOf(s)).collect(Collectors.toList());
        //通过id查询用户并转换成UserDTO
        List<UserDTO> userDTOS = userService.listByIds(top5).stream().map(user -> BeanUtil.copyProperties(user, UserDTO.class)).collect(Collectors.toList());
        return Result.ok(userDTOS);
    }

    @Override
    public Result getUserBlog(Long id, Long current) {
        Page<Blog> blogPage = this.lambdaQuery().eq(Blog::getUserId, id).page(new Page<>(current, SystemConstants.MAX_PAGE_SIZE));
        List<Blog> blogs = blogPage.getRecords();
        if (blogs == null || blogs.isEmpty() ){
            return Result.ok(Collections.emptyList());
        }
        return Result.ok(blogs);
    }

    @Override
    public Result saveBlog(Blog blog) {
        // 获取登录用户
        UserDTO user = UserHolder.getUser();
        blog.setUserId(user.getId());
        if (blog.getTitle() == null ){
            return Result.fail("笔记标题不能为空");
        }
        // 保存探店博文
        boolean isSave = save(blog);
        if (!isSave){
            return Result.fail("新增笔记失败");
        }
        //获取所以关注
        List<Follow> follows = followService.lambdaQuery().eq(Follow::getFollowUserId, user.getId()).list();

        for (Follow follow : follows) {
            Long userId = follow.getUserId();
            stringRedisTemplate.opsForZSet().add(FEED_KEY+userId,blog.getId().toString(),System.currentTimeMillis());
        }

        // 返回id
        return Result.ok(blog.getId());
    }

    @Override
    public Result getFollowBlog(Long lastID, Integer offset) {
        Long userId = UserHolder.getUser().getId();
        //查询收件箱
        Set<ZSetOperations.TypedTuple<String>> typedTuples = stringRedisTemplate.opsForZSet().reverseRangeByScoreWithScores(FEED_KEY + userId, 0, lastID, offset, 2);
        if (typedTuples == null || typedTuples.isEmpty() ){
            return Result.ok();
        }
        //解析查询到的数据
        List<Long> ids = new ArrayList<>(typedTuples.size());
        int os = 1;
        long minTime = 0;
        for (ZSetOperations.TypedTuple<String> typedTuple : typedTuples) {
            ids.add(Long.valueOf(typedTuple.getValue()));
            long time = typedTuple.getScore().longValue();
            if (time == minTime){
                os++;
            }else {
                minTime = time;
                os = 1;
            }
        }
        //根据id获取blog
        List<Blog> blogs = lambdaQuery().in(Blog::getId, ids).orderBy(true, false, Blog::getCreateTime).list();
        for (Blog blog : blogs) {
            //查询点赞用户
            queryBlogById(blog.getId());
            //查询是否点赞
            isBlogLiked(blog);
        }
        ScrollResult result = new ScrollResult();
        result.setList(blogs);
        result.setOffset(os);
        result.setMinTime(minTime);
        return Result.ok(result);
    }

    private void isBlogLiked(Blog blog) {
        UserDTO user = UserHolder.getUser();
        if (user == null){
            return;
        }
        Long userId = user.getId();

        String blog_Liked_Key = BLOG_LIKED_KEY + blog.getId();
        Double score = stringRedisTemplate.opsForZSet().score(blog_Liked_Key, userId.toString());
        blog.setIsLike(score != null);
    }


    @Override
    public Result queryBlogById(Long id) {
        Blog blog = getById(id);
        if (blog == null) {
            return Result.fail("博客不存在");
        }
        //设置blog的用户信息
        this.blogSetUser(blog);

        //是否点赞
        this.isBlogLiked(blog);

        return Result.ok(blog);
    }

    private Long blogSetUser(Blog blog) {

        Long userId = blog.getUserId();
        User user = userService.getById(userId);
        blog.setIcon(user.getIcon());
        blog.setName(user.getNickName());
        return userId;
    }

    @Override
    public Result likeBlog(Long id) {
        String blog_Liked_Key = BLOG_LIKED_KEY + id;
        String userId = UserHolder.getUser().getId().toString();

        //1.判断用户是否点过赞
        Double score = stringRedisTemplate.opsForZSet().score(blog_Liked_Key, userId);
        if (score == null) {
            //没点过赞
            boolean isSuccess = lambdaUpdate().eq(Blog::getId, id).setSql("liked = liked + 1").update();
            if (isSuccess) {
                stringRedisTemplate.opsForZSet().add(blog_Liked_Key, userId,System.currentTimeMillis());
            }
        } else {
            //点过赞
            boolean isSuccess = lambdaUpdate().eq(Blog::getId, id).setSql("liked = liked - 1").update();
            if (isSuccess) {
                stringRedisTemplate.opsForZSet().remove(blog_Liked_Key, userId);
            }

        }

        return Result.ok();
    }
}
  • 二.
    followController.java
@RestController
@RequestMapping("/follow")
public class FollowController {
    @Resource
    IFollowService followService;

    @PutMapping("/{id}/{isFollow}")
    public Result follow(@PathVariable("id") Long followUserId,@PathVariable("isFollow") Boolean isFollow){
        return followService.follow(followUserId,isFollow);
    }
    @GetMapping("/or/not/{id}")
    public Result isFollow(@PathVariable("id") Long followUserId){
        return followService.isFollow(followUserId);
    }

    @GetMapping("/common/{id}")
    public Result commonConcern(@PathVariable Long id){
        return followService.commonConcern(id);
    }
}

followServiceImpl.java

@Service
public class FollowServiceImpl extends ServiceImpl<FollowMapper, Follow> implements IFollowService {

    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private IUserService userService;
    //关注和取关
    @Override
    public Result follow(Long followUserid, Boolean isFollow) {
        Long userId = UserHolder.getUser().getId();
        String followKey = "follows"+userId;
        if (isFollow) {
            //初始化follow
            Follow follow = new Follow();
            follow.setUserId(userId);
            follow.setFollowUserId(followUserid);
            follow.setCreateTime(LocalDateTime.now());
            //关注
            boolean isSave = this.save(follow);
            if (isSave){
                stringRedisTemplate.opsForSet().add(followKey,followUserid.toString());
            }

            return Result.ok();
        }
        //取关
        boolean isRemove = this.lambdaUpdate().eq(Follow::getUserId, userId).eq(Follow::getFollowUserId, followUserid).remove();
        if (isRemove) {
            stringRedisTemplate.opsForSet().remove(followKey,followUserid.toString());
        }
        return Result.ok();
    }
//查询是否关注
    @Override
    public Result isFollow(Long followUserId) {
        Long userId = UserHolder.getUser().getId();
        //查询是否关注
        Integer count = this.lambdaQuery().eq(Follow::getUserId, userId).eq(Follow::getFollowUserId, followUserId).count();

        return Result.ok(count > 0);
    }

    @Override
    public Result commonConcern(Long id) {
        Long userId = UserHolder.getUser().getId();
        String myKey = "follows"+userId;
        String otherKey =  "follows"+id;
        Set<String> resultSet = stringRedisTemplate.opsForSet().intersect(myKey, otherKey);
        if (resultSet == null || resultSet.isEmpty()){
            return Result.ok();
        }
        //Set<String>转List<Long>
        List<Long> resultList = resultSet.stream().map(s -> Long.valueOf(s)).collect(Collectors.toList());
        List<UserDTO> users = userService.listByIds(resultList).stream()
                .map(user -> BeanUtil.copyProperties(user, UserDTO.class)).collect(Collectors.toList());
        if (users == null || users.isEmpty()){
            return Result.ok(Collections.emptyList());
        }
        return Result.ok(users);
    }
}

geo类型

常用类型api
geo类型api
在这里插入图片描述
在这里插入图片描述

bitMap

在这里插入图片描述

在这里插入图片描述

hyperLogLog

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

八.持久化

在这里插入图片描述
在这里插入图片描述

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值