Redis学习笔记(四) | 青训营笔记

57 阅读2分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 4 天

前面学习和记录了缓存穿透这一问题,接下来继续学习项目开发时可能会遇到的与缓存相关的问题。本次记录的是关于缓存雪崩和缓存击穿的学习笔记。

一.缓存雪崩

  • 发生原因:同一时段大量缓存同时失效,或redis服务宕机导致大量请求到达数据库
  • 解决方案
    • 给不同的key的TTL添加随机值,防止同时失效
    • 利用redus集群提高服务的可用性
    • 给缓存业务添加降级限流策略
    • 给业务添加多级缓存

二.缓存击穿

  • 一个被高并发访问并且缓存重建业务较复杂的key突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击
  • 解决方案
    • 互斥锁
      • 原理:重建过程中一个线程上锁,其它线程获取锁失败则等待,直到那个线程重建缓存完毕释放锁,但性能可能比较差
      • 案例
      public Shop queryWithMutex(Long id){
      String key = RedisConstants.CACHE_SHOP_KEY + id;
      Shop shop = new Shop();
      try {
          shop = redisService.getObject(key, Shop.class);
      
          if(!Objects.isNull(shop)){
              return shop;
          }else {
              String s = redisService.get(key);
              if(s.equals("null")){
                  return null;
              }
          }
      } catch (Exception e){
          log.info("null");
      }
      
      //1.实现缓存重建互斥锁
      //1.1.获取互斥锁
      String lockKey = RedisConstants.LOCK_SHOP_KEY + id;
      try {
          boolean isLock = tryLock(lockKey);
          //1.2.判断是否获取成功
          if(!isLock){
              //不成功则休眠,并重新尝试
              Thread.sleep(50);
              return queryWithMutex(id);
          }
          //1.3.成功,根据id查询数据库
          shop = getById(id);
          //模拟重建延迟
          //Thread.sleep(2000);
          if(Objects.isNull(shop)){
              //保存空值,针对缓存穿透问题
              redisService.add(key,"null",RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);
              return null;
          }
          //1.4.写入redis
          redisService.add(key,shop,RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);
      } catch (InterruptedException e) {
          throw new RuntimeException(e);
      } finally {
          //1.5.释放锁
          unlock(lockKey);
      }
      
      return shop;
      }
      
    • 逻辑过期
      • 对于缓存入redis的数据自定义增加一个字段用于记录过期时间,当每次获取数据时,先判断数据是否过期,没过期则直接返回,若过期则上锁,开启独立线程用于重构数据,并先将旧数据返回。此时若其它线程请求同一份数据,因为获取不了锁所以不能开启线程进行数据重构,此时就直接将旧数据返回。