Redis实战篇(一)验证码登录、缓存穿透、缓存击穿、缓存雪崩的讲解以及解决

目录

基于session实验短信登录

 发送验证码

 登录验证功能​编辑

 用Redis实现共享Session问题

登录拦截器的优化

商品查询缓存

什么是缓存

添加redis缓存

 缓存穿透

 缓存雪崩

 缓存击穿

 缓存工具封装​编辑


基于session实验短信登录

 发送验证码

 

 登录验证功能

 

 

 用Redis实现共享Session问题

 

 

 

登录拦截器的优化


public class LoginInterceptor implements HandlerInterceptor {


//登录优化后 创建RefreshTokenInterceptor
@Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        //1.判断是否需要拦截(ThreadLocal是否用户)
    if ( UserHolder.getUser()==null ){
        //没有,需要拦截,设置状态码
        response.setStatus(401);
        //拦截
        return false;

    }
//    有用户,则放行
    return true;
    }
//登录优化前

//    @Override
//    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//        //1.获取session
//        //HttpSession session = request.getSession();
//        //TODO 1.获取请求头中的token
//        String token = request.getHeader("authorization");
//        if ( StrUtil.isBlank(token) ) {
//            //不存在,拦截,返回401状态码
//            response.setStatus(401);
//            return false;
//        }
//        //2.获取session中的用户
        Object user = session.getAttribute("user");
//        //TODO 2.基于TOKEN获取redis中的用户
//        String key = RedisConstants.LOGIN_USER_KEY + token;
//        Map<Object, Object> userMap = stringRedisTemplate.opsForHash()
//                .entries(key);
//
//        //3.判断用户是否存在
//        if ( userMap.isEmpty()){
//         //4.不存在,拦截 返回401状态码
//        response.setStatus(401);
//        return false;
//        }
//        //TODO 5.将查询到的Hash数据转为UserDTO对象
//        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
//        //TODO 6.存在,保存用户信息到ThreadLocal
//        UserHolder.saveUser(userDTO);
//        //5.存在,保存用户信息到ThreadLocal
//        //5.存在,保存用户信息到ThreadLocal
//       // UserHolder.saveUser((UserDTO) user);
//        //TODO 7.刷新token有效期
//        stringRedisTemplate.expire(key,RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);
//        //6.放行
//        return true;
//    }



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

    }
}

 


public class RefreshTokenInterceptor implements HandlerInterceptor {

    private StringRedisTemplate stringRedisTemplate;

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

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //1.获取session
        //HttpSession session = request.getSession();
        //TODO 1.获取请求头中的token
        String token = request.getHeader("authorization");
        if ( StrUtil.isBlank(token) ) {
            //不存在,拦截,返回401状态码
//            response.setStatus(401);
            return true;
        }
        //2.获取session中的用户
//        Object user = session.getAttribute("user");
        //TODO 2.基于TOKEN获取redis中的用户
        String key = RedisConstants.LOGIN_USER_KEY + token;
        Map<Object, Object> userMap = stringRedisTemplate.opsForHash()
                .entries(key);

        //3.判断用户是否存在
        if ( userMap.isEmpty()){
         //4.不存在,拦截 返回401状态码
//        response.setStatus(401);
        return true;
        }
        //TODO 5.将查询到的Hash数据转为UserDTO对象
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
        //TODO 6.存在,保存用户信息到ThreadLocal
        UserHolder.saveUser(userDTO);
        //5.存在,保存用户信息到ThreadLocal
        //5.存在,保存用户信息到ThreadLocal
       // UserHolder.saveUser((UserDTO) user);
        //TODO 7.刷新token有效期
        stringRedisTemplate.expire(key,RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);
        //6.放行
        return true;
    }



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

    }
}

商品查询缓存

什么是缓存

 

添加redis缓存

 

 

 

 

 

 缓存穿透

 

 

//    TODO 封装缓存穿透
    public Shop queryWithPassThrough(Long id){
        String key = CACHE_SHOP_KEY + id;
        //1、从redis查询商品缓存
        String shopJson = stringRedisTemplate.opsForValue().get(key);

        //2.判断是否存在
        if ( StrUtil.isNotBlank(shopJson) ){
            //TODO 如果是“”也是返回false
            //3.存在,直接返回
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
//            return Result.ok(shop);
            return shop;
        }

        //TODO 判断命中的是否是空值
        if ( shopJson!=null ){//第一步判断后这里就只剩下null和""
//            或者 if ( "".equals(shopJson) )
            //返回一个错误信息
//            return Result.fail("店铺不存在!!!");

            return null;

        }

        //4、不存在,根据id查询数据库
        Shop shop = getById(id);
        //5、不存在,返回错误
        if ( shop==null ){
            //TODO  解决缓存穿透
            //将控制写入redis中
            stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL,TimeUnit.MINUTES);
//            return Result.fail("店铺不存在");
            return null;
        }
        //6、存在,写入redis
        stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES);
        //7、返回
        return shop;
    }

 缓存雪崩

 缓存击穿

 

 

 //    TODO 封装缓存击穿
    public Shop queryWithMutex(Long id){
        String key = CACHE_SHOP_KEY + id;
        //1、从redis查询商品缓存
        String shopJson = stringRedisTemplate.opsForValue().get(key);

        //2.判断是否存在
        if ( StrUtil.isNotBlank(shopJson) ){
            //TODO 如果是“”也是返回false
            //3.存在,直接返回
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
//            return Result.ok(shop);
            return shop;
        }

        //TODO 判断命中的是否是空值
        if ( shopJson!=null ){//第一步判断后这里就只剩下null和""
//            或者 if ( "".equals(shopJson) )
            //返回一个错误信息
//            return Result.fail("店铺不存在!!!");
            return null;

        }
        //ToDO 4.实现缓存重建
        //ToDO 4.1获取互斥锁
        String lockKey = LOCK_SHOP_KEY + id;
        Shop shop = null;
        try {
            boolean isLock = tryLock(lockKey);
            //ToDO 4.2判断是否获取成功
            if ( !isLock ){
                //ToDO 4.3失败,则休眠并重试
                Thread.sleep(50);
                return queryWithMutex(id);

            }

            //ToDO 4.4、成功,根据id查询数据库
            shop = getById(id);
            //模拟重建的延时
            Thread.sleep(200);
            //5、不存在,返回错误
            if ( shop==null ){
                //TODO  解决缓存穿透
                //将控制写入redis中
                stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL,TimeUnit.MINUTES);
    //            return Result.fail("店铺不存在");
                return null;
            }
            //6、存在,写入redis
            stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES);
        }
        catch (InterruptedException e) {
            throw new RuntimeException(e);
        }finally {
            //TODO 释放互斥锁
            unlock(lockKey);
        }


        //7、返回
        return shop;
    }





 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);
    }

 缓存工具封装


@Slf4j
@Component
public class CacheClient {


    private final StringRedisTemplate stringRedisTemplate;

    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));
    }

    //    TODO 封装缓存穿透
    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) ){
            //TODO 如果是“”也是返回false
            //3.存在,直接返回
            return JSONUtil.toBean(json, type);
        }

        //TODO 判断命中的是否是空值
        if ( json!=null ){//第一步判断后这里就只剩下null和""
//            或者 if ( "".equals(shopJson) )
            //返回一个错误信息
            return null;

        }

        //4、不存在,根据id查询数据库
        R r = dbFallback.apply(id);
        //5、不存在,返回错误
        if ( r==null ){
            //TODO  解决缓存穿透
            //将控制写入redis中
            stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL,TimeUnit.MINUTES);
//            return Result.fail("店铺不存在");
            return null;
        }
        //6、存在,写入redis
        this.set(key,r,time,unit);
//        stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(r),CACHE_SHOP_TTL, TimeUnit.MINUTES);
        //7、返回
        return r;
    }

    //TODO 线程池
    private  static  final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);

    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) ){
            //TODO 如果是“”也是返回false
            //3.不存在,直接返回
            return null;
        }

        //4.命中,需要先把json反序列化为对象
        RedisData redisData = JSONUtil.toBean(json, RedisData.class);
        JSONObject data = (JSONObject)redisData.getData();
        R r = JSONUtil.toBean(data, 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 ) {
            //TODO 6.3成功,开启独立线程,实现缓存重建
            CACHE_REBUILD_EXECUTOR.submit(()->{
                try {
                    //重建缓存(分下面两步)
                    //查询数据库
                    R r1 = dbFallback.apply(id);
                    //写入redis
                    this.setWithLogicalExpire(key,r1,time,unit);

                }
                catch (Exception e) {
                    e.printStackTrace();
                }finally {
                    //释放锁
                    unlock(lockKey);
                }

            });
        }
        //6.4失败,返回过期的商铺信息
        //7、返回
        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);
    }


}

 

 

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值