谷粒商城 高级篇 (九) --------- 缓存失效问题

目录

  • 一、缓存穿透
  • 二、缓存雪崩
  • 三、缓存击穿
  • 四、本地加锁解决缓存击穿问题


先来解决大并发读情况下的缓存失效问题。

一、缓存穿透

缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中,将去查询数据库,但是数据库也无此记录,我们没有将这次查询的 null 写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。

在流量大时,可能 DB 就挂掉了,要是有人利用不存在的 key 频繁攻击我们的应用,这就是漏洞。

解决:

缓存空结果、并且设置短的过期时间。

谷粒商城 高级篇 (九) --------- 缓存失效问题_第1张图片

二、缓存雪崩

缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到 DB,DB 瞬时压力过重雪崩。

解决:

原有的失效时间基础上增加一个随机值,比如 1-5 分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。

谷粒商城 高级篇 (九) --------- 缓存失效问题_第2张图片

三、缓存击穿

对于一些设置了过期时间的 key,如果这些 key 可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。

这个时候,需要考虑一个问题:如果这个 key 在大量请求同时进来前正好失效,那么所有对这个 key 的数据查询都落到 db,我们称为缓存击穿。

解决:

加锁

谷粒商城 高级篇 (九) --------- 缓存失效问题_第3张图片

四、本地加锁解决缓存击穿问题

// 1. 空结果缓存, 解决缓存穿透
// 2. 设置过期时间(加随机值), 解决缓存雪崩
// 3. 加锁, 解决缓存击穿
@Override
public Map<String, List<Catelog2Vo>> getCatelogJson() {
    // 给缓存中放 json 字符串, 拿出的 json 字符串 , 还能逆转为 能用的对象 [序列化与反序列化]
    // 1. 加入缓存逻辑 缓存中存的数据是 json 字符串
    // json 跨语言、跨平台兼容
    String catelogJSON = redisTemplate.opsForValue().get("catelogJSON");
    if (StringUtils.isEmpty(catelogJSON)) {
        // 2. 缓存中没有, 查询数据库
        Map<String, List<Catelog2Vo>> catelogJsonFromDB = getCatalogJsonFromDB();

        // 3. 查到的数据再放入缓存, 将对象转为 json 放在缓存中
        

        return catelogJsonFromDB;
    }

    // 转为指定的对象
    Map<String, List<Catelog2Vo>> result = JSON.parseObject(catelogJSON, new TypeReference<Map<String, List<Catelog2Vo>>>(){});
    return  result;
}

public  Map<String, List<Catelog2Vo>> getCatalogJsonFromDB() {
    
    
    // 只要是同一把锁, 就能锁住需要这个锁的所有线程
    // 1. synchronized (this) : SpringBoot 所有的组件在容器中都是单例的
    //1. 查出所有 1 级分类
   
   
   synchronized (this) {
       
       // 得到锁以后, 我们应该再去缓存中确定一次, 如果没有才需要继续查询
       // TODO 本地锁 synchronized , JUC(lock) 分布式场景下想要锁住所有 需要分布式锁

       String catelogJSON = redisTemplate.opsForValue().get("catelogJSON");
       if (!StringUtils.isEmpty(catelogJSON)) {
           // 缓存不为 null 直接返回
           Map<String, List<Catelog2Vo>> result = JSON.parseObject(catelogJSON, new TypeReference<Map<String, List<Catelog2Vo>>>(){});
           return  result;
       }
       List<CategoryEntity> level1Categorys = getLevel1Categorys();

       //2. 封装数据
       Map<String, List<Catelog2Vo>> parent_cid = level1Categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {

           // 1. 每一个一级分类, 查到这个一级分类的二级分类
           List<CategoryEntity> categoryEntities = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", v.getCatId()));

           List<Catelog2Vo> catelog2Vos = null;
           if (categoryEntities != null) {
               catelog2Vos = categoryEntities.stream().map(l2 -> {
                   Catelog2Vo catelog2Vo = new Catelog2Vo(v.getCatId().toString(), null, l2.getCatId().toString(), l2.getName());
                   // 找当前二级分类的三级分类封装成vo
                   List<CategoryEntity> level3Catelog = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", l2.getParentCid()));
                   if (level3Catelog != null) {
                       List<Catelog2Vo.Category3Vo> collect = level3Catelog.stream().map(l3 -> {
                           // 封装成指定格式
                           Catelog2Vo.Category3Vo category3Vo = new Catelog2Vo.Category3Vo(l2.getCatId().toString(), l3.getCatId().toString(), l3.getName());
                           return category3Vo;
                       }).collect(Collectors.toList());
                       catelog2Vo.setCatalog3List(collect);

                   }
                   return catelog2Vo;
               }).collect(Collectors.toList());
           }
           return catelog2Vos;

       }));

       String s = JSON.toJSONString(parent_cid);
       redisTemplate.opsForValue().set("catelogJSON", s, 1, TimeUnit.DAYS);
       return parent_cid;
   }
}

你可能感兴趣的:(微服务项目,缓存,java,redis)