• 仿黑马点评-redis整合【二——商户查询缓存】——缓存穿透、缓存击穿的解决


    前言
    👏作者简介:我是笑霸final,一名热爱技术的在校学生。
    📝个人主页:个人主页1 || 笑霸final的主页2
    📕系列专栏:《项目专栏》
    📧如果文章知识点有错误的地方,请指正!和大家一起学习,一起进步👀
    🔥如果感觉博主的文章还不错的话,👍点赞👍 + 👀关注👀 + 🤏收藏🤏

    在这里插入图片描述

    🐉商户查询缓存 介绍

    上节回顾

    仿黑马点评-redis整合【邮件登陆部分】点此查看

    本节梳理

    解决缓存穿透、缓存击穿的问题

    🐉添加redis缓存

    流程
    在这里插入图片描述

    代码

    @Override
        public Result queryById(Long id) {
            //1.从redis查询缓存
            //String shopJson = stringRedisTemplate.opsForValue().get("cache:shop" + id);
            //这里的cache:shop可以写成字符串常量
            String shopJson = stringRedisTemplate.opsForValue().get(RedisConstants.CACHE_SHOP_KEY + id);
            //2.判断是否存在
            if (StrUtil.isNotBlank(shopJson)) {
                //3.存在返回
                //3.1把json字符串转化为对象
                Shop shop = JSONUtil.toBean(shopJson, Shop.class);
                return Result.ok(shop);
            }
            //4.不存在,根据id查数据库
            Shop shop = getById(id);
    
            //5.不存在,返回错误
            if (shop==null) return Result.fail("没有此店铺");
            //6.存在写入redis
            String toJsonStr = JSONUtil.toJsonStr(shop);//转成json字符串
            stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY + id,toJsonStr);
            //7.返回
            return Result.ok(shop);//这返回的是对象
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    🐉给店铺类型查询添加缓存(作业)

    分析步骤我们还是可以拿上面的图
    在这里插入图片描述
    代码

     @Autowired
        private StringRedisTemplate stringRedisTemplate;
        @Override
        public Result findList() {
    
            //1.从redis查询缓存
            //构造key
            String s = stringRedisTemplate.opsForValue().get("cache:list");
            //2.判断是否存在
            if (StrUtil.isNotBlank(s)) {
                //3.存在返回
                List<ShopType> dtoList=JSONUtil.toList(s,ShopType.class);
                log.info("redis返回的数据");
                return Result.ok(dtoList);
            }
            //4.不存在,根据id查数据库
            List<ShopType> typeList = this.query().orderByAsc("sort").list();
            //5.不存在,返回错误
            if (typeList.isEmpty()) {
                return Result.fail("错误");
            }
            //6.存在写入redis
            String s1 = JSONUtil.toJsonStr(typeList);
            stringRedisTemplate.opsForValue().set("cache:list",s1);
            //7.返回
            return Result.ok(typeList);//这返回的是对象
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    效果

    • 第一次查询
      在这里插入图片描述
      在这里插入图片描述
    • 第二次查询
      *

    🐉缓存的更新策略

    缓存的更新策略的方案在这里插入图片描述
    上面加入redis缓存我自己加了超时剔除

    🐉缓存存在的问题

    下面代码访问的路径 http://localhost:8081/shop/1

    1.缓存穿透

    缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库。
    常见的解决方案有两种:

    • 缓存空对象
      优点:实现简单,维护方便
      缺点:额外的内存消耗,可能造成短期的不一致
      适合命中不高,但可能被频繁更新的数据
    • 布隆过滤
      优点:内存占用较少,没有多余key
      缺点:实现复杂,存在误判可能
      适合命中不高,但是更新不频繁的数据
      在这里插入图片描述
     @Override
        public Result queryById(Long id) {
            //1.从redis查询缓存
            //String shopJson = stringRedisTemplate.opsForValue().get("cache:shop" + id);
            //这里的cache:shop可以写成字符串常量
            String shopJson = stringRedisTemplate.opsForValue().get(RedisConstants.CACHE_SHOP_KEY + id);
            //2.判断是否存在
            if (StrUtil.isNotBlank(shopJson)) {
                //3.存在返回
                //3.1把json字符串转化为对象
                Shop shop = JSONUtil.toBean(shopJson, Shop.class);
                return Result.ok(shop);
            }
            if(shopJson==""){
                return Result.fail("不存在此店铺");
            }
            //4.不存在,根据id查数据库
            Shop shop = getById(id);
            if(shop==null){
                //解决缓存穿透 存入空值
                //stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY + id,null,2, TimeUnit.MINUTES);
                stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY + id,"",2, TimeUnit.MINUTES);
                //这里不能用null,值为null会查数据库
                return Result.fail("不存在此店铺");
            }
            //5.存在写入redis
            String toJsonStr = JSONUtil.toJsonStr(shop);//转成json字符串
            stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY + id,toJsonStr,2, TimeUnit.MINUTES);
            //6.返回
            return Result.ok(shop);//这返回的是对象
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    2.缓存雪崩

    缓存雪崩是指在同一时段大量的缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库,带来巨大压力。
    解决方案:

    • 给不同的Key的TTL添加随机值
    • 利用Redis集群提高服务的可用性
    • 给缓存业务添加降级限流策略
    • 给业务添加多级缓存

    3.缓存击穿

    缓存击穿问题也叫热点Key问题,就是一个被高并发访问并且缓存重建业务较复杂的key突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击。
    常见的解决方案有两种:

    • 互斥锁
    • 逻辑过期
      在这里插入图片描述

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

    互斥锁

    互斥锁解决方法

    最大的问题就是互相等待
    在这里插入图片描述
    相关方法
    在这里插入图片描述

     public Result queryById(Long id) {
            //1.从redis查询缓存
            //String shopJson = stringRedisTemplate.opsForValue().get("cache:shop" + id);
            //这里的cache:shop可以写成字符串常量
            String shopJson = stringRedisTemplate.opsForValue().get(RedisConstants.CACHE_SHOP_KEY + id);
            //2.判断是否存在
            if (StrUtil.isNotBlank(shopJson)) {
                //3.存在返回
                //3.1把json字符串转化为对象
                Shop shop = JSONUtil.toBean(shopJson, Shop.class);
                return Result.ok(shop);
            }
            if(shopJson!=null){
                return Result.fail("不存在此店铺");
            }
            //4不存在,实现缓存重建
            String lockKey="Key:shop:"+id;
            try {
                //4.1.获取互斥锁
                boolean isLock = tryLock(lockKey);
                //4.2.判断是否获取成功
                if (!isLock){
                    //4.3失败休眠并重试
                    Thread.sleep(50);
                    return queryById(id);
                }
                //4.4.成功
                //成功应该再次检测redis缓存是否存在 这里不演示了
                Shop shop = getById(id);
                if(shop==null){
                    //解决缓存穿透 存入空值
                    //stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY + id,null,2, TimeUnit.MINUTES);
                    stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY + id,"",2, TimeUnit.MINUTES);
                    //这里不能用null,值为null会查数据库
                    return Result.fail("不存在此店铺");
                }
                //5.存在写入redis
                String toJsonStr = JSONUtil.toJsonStr(shop);//转成json字符串
                stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY + id,toJsonStr,2, TimeUnit.MINUTES);
                //6.返回
                return Result.ok(shop);//这返回的是对象
                
            }catch (InterruptedException e) {
                throw  new RuntimeException(e);
            }finally {
                //释放互斥锁
                unlock(lockKey);
            }
            
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    逻辑过期

    逻辑过期解决方法

    在这里插入图片描述
    相关数据
    在这里插入图片描述
    在这里插入图片描述
    线程池在这里插入图片描述

     String key = RedisConstants.CACHE_SHOP_KEY + 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);
            JSONObject data = (JSONObject) redisData.getData();
            Shop shop = JSONUtil.toBean(data,Shop.class);
            LocalDateTime expireTime = redisData.getExpireTime();
            //5.判断是否过期
            if(expireTime.isAfter(LocalDateTime.now())){
                //5.1未过期,直接返回
                return Result.ok(shop);
            }
    
            //5.2已过期,需要缓存重建
    
            //6.缓存重建
            //6.1获取互斥锁
            String lockkey = "lock:shop:" + id;
            boolean lock = tryLock(lockkey);
            //6.2判断是否获取锁成功
            if(lock){
                //注意 锁获取成功应该再次检测redis缓存是否过期(这里不演示)
                //6.3成功,开启独立线程,实现缓存重建
                //利用线程池去完成
                ExcutorService_CACHE_RECUTOR.submit(()->{
                    //重建缓存
                    try {
                        this.saveShopToRdis(id,30L);
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }finally {
                        //释放锁
                        unlock(lockkey);
                    }
    
                });
    
            }
    
            //6.4返回商铺信息
            return Result.ok(shop);
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    🔥如果感觉博主的文章还不错的话,👍点赞👍 + 👀关注👀 + 🤏收藏🤏

  • 相关阅读:
    C语言几个比较实用的宏定义
    吃透Redis(九):缓存淘汰篇-LFU算法
    嵌入式系统,ARM微处理器特点,ARM体系结构,特征、状态、操作模式等,中断分类,JTAG调试接口
    ES6中的Promise
    C++ Reference: Standard C++ Library reference: C Library: cwchar: fgetwc
    printf 是怎么舍入的
    springmvc 03(JSR303和拦截器)
    QCC51XX---Kymera调节音量
    CentOS7安全配置
    Java基础学习总结(195)—— 关于 Java 8 中的日期处理总结
  • 原文地址:https://blog.csdn.net/weixin_52062043/article/details/127608572