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

时间:2022-11-02 09:54:08

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

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

????商户查询缓存 介绍

上节回顾

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

本节梳理

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

????添加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);//这返回的是对象
    }

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

分析步骤我们还是可以拿上面的图
仿黑马点评-redis整合【二——商户查询缓存】——缓存穿透、缓存击穿的解决
代码

 @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);//这返回的是对象
    }

效果

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

????缓存的更新策略

缓存的更新策略的方案仿黑马点评-redis整合【二——商户查询缓存】——缓存穿透、缓存击穿的解决
上面加入redis缓存我自己加了超时剔除

????缓存存在的问题

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

1.缓存穿透

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

  • 缓存空对象
    优点:实现简单,维护方便
    缺点:额外的内存消耗,可能造成短期的不一致
    适合命中不高,但可能被频繁更新的数据
  • 布隆过滤
    优点:内存占用较少,没有多余key
    缺点:实现复杂,存在误判可能
    适合命中不高,但是更新不频繁的数据
    仿黑马点评-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);
        }
        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);//这返回的是对象
    }

2.缓存雪崩

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

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

3.缓存击穿

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

  • 互斥锁
  • 逻辑过期
    仿黑马点评-redis整合【二——商户查询缓存】——缓存穿透、缓存击穿的解决

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

互斥锁

互斥锁解决方法

最大的问题就是互相等待
仿黑马点评-redis整合【二——商户查询缓存】——缓存穿透、缓存击穿的解决
相关方法
仿黑马点评-redis整合【二——商户查询缓存】——缓存穿透、缓存击穿的解决

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

逻辑过期

逻辑过期解决方法

仿黑马点评-redis整合【二——商户查询缓存】——缓存穿透、缓存击穿的解决
相关数据
仿黑马点评-redis整合【二——商户查询缓存】——缓存穿透、缓存击穿的解决
仿黑马点评-redis整合【二——商户查询缓存】——缓存穿透、缓存击穿的解决
线程池仿黑马点评-redis整合【二——商户查询缓存】——缓存穿透、缓存击穿的解决

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

    }

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