图示Redis缓存雪崩、穿透、击穿及常用解决方案-java案例

Redis雪崩
  • 雪崩概念

    当redis中大量缓存的过期时间相同时,缓存到达过期时间集体失效(雪崩效应),大量请求绕过缓存层直接访问数据库load数据,导致数据库频繁IO,性能下降乃至宕机崩溃。

  • 图解雪崩过程雪崩示意图

    一个简单代码层图示雪崩后未处理的后果

    代码层出现雪崩未处理后果图解

  • 常用解决方案

    • 分散过期时间

      随机redis缓存的过期时间,使得缓存失效时间点分散开来。

    • mq削峰策略

      依靠mq削峰特性,发生雪崩效应时load数据过程走mq同步消息方式,mq消息放入顺序队列中,消费者消费第一个消息从数据库load数据并重新加入到缓存中,后续的相同消息则直接从缓存返回无需访问数据库。

      mq削峰解决缓存雪崩图解

    • 锁方式

      /**
           * 获取字典信息 雪崩后加锁
           *
           * @param key
           * @return
           */
      public String getDictInfoCrashByLock(String key) {
          //从redis中获取值
          String dictValue = getFromRedis(key);
          if (dictValue == null) {
              //这里以本地可重入锁方式解决单机
              // 如果分布式环境就得换分布式锁比如redis分布式锁或zookeeper分布式锁
              Lock lock = new ReentrantLock();
              lock.lock();
              try {
                  //当redis中不存在时访问数据库
                  dictValue = sysDictDbService.loadData(key);
                  //从数据库中加载数据并放入redis
                  if (dictValue != null) {
                      setValueToRedis(key, dictValue);
                  }
              } finally {
                  lock.unlock();
              }
          }
          return dictValue;
      }
      

      锁的形式可以保证重新载入数据的执行只有一个线程执行。这样就避免了大流量的访问数据库。

    • 限流法

      限流方式也类似锁的形式,为了控制雪崩效应后请求数据库的流量。

      具体限流算法和实现可以参考文章:

      https://blog.csdn.net/m0_43430744/article/details/84789624

Redis缓存穿透
  • 穿透概念

    缓存穿透是指请求查询的数据在缓存中不存在且在数据库中也不存在,比如redis和数据库中都只有id为1、2、3的数据,而请求查询id为4、5、6等不存在的数据,这类请求是每次都会穿透redis缓存访问数据库,如果程序未加以防范则可能会被恶意攻击。

  • 图解穿透过程

    缓存穿透示意图

  • 常用解决方案

    • 规则排除

      如上面id查询,我们可以定义一个符合id规则的过滤器,如果不符合规则的请求(如xxx)则直接返回空。

    • null值填充

      当缓存穿透时,redis存入一个类似null的值,下次访问则直接缓存返回空,当数据库中存在该数据的值则需要把redis存在的null值清除并载入新值,此方案不能解决频繁随机不规则的key请求。

       /**
           * 获取字典信息-穿透redis存<null>
           *
           * @param key
           * @return
           */
          public String getDictInfoStrikeByNull(String key) {
              String nullTag = "<null>";
              //1、从redis中获取值
              String dictValue = getFromRedis(key);
              if (dictValue == null) {
                  //当redis中不存在时访问数据库
                  dictValue = sysDictDbService.loadData(key);
                  //从数据库中查询不到数据则redis填充<null>
                  String toRedisValue =    Optional.ofNullable(dictValue).orElse(nullTag);
                  setValueToRedis(key, toRedisValue);
              }
              //此方法需依赖数据库插入新数据是需要清除redis已经存在的<null>标记值
              //如果从redis取得是null标记字符则也返回null
              return nullTag.equals(dictValue) ? null : dictValue;
          }
      
    • 一级二级缓存法/布隆过滤器('糊涂’工具小试)

      此方法解决方案就是类型一个bitmap存放对应数据的标记为,1表示有数据0表示无数据,,一般是查询id的hashkey做hash取模算法快速定位所在bitmap位置的标记位,当数据库插入数据时往对应位置标记为1表示有数据,下次走redis缓存前先访问这个bitmap,如果不存在则返回空。以下使用github上开源工具类hutool的布隆过滤器演示。

      maven依赖

      <dependency>
          <groupId>cn.hutool</groupId>
          <artifactId>hutool-all</artifactId>
          <version>4.2.1</version>
      </dependency>
      

      简化实现代码

        	//hutool bitmap布隆过滤器
          private BloomFilter bloomFilter = new BitMapBloomFilter(10000);
      
          /**
           * 是否有效key
           *
           * @param key
           * @return
           */
          private boolean bloomContains(String key) {
              return bloomFilter.contains(key);
          }
      
          /**
           * 添加到布隆过滤器中,一般数据库插入时候添加
           *
           * @param key
           */
          private void addKeyToBloomFilter(String key) {
              bloomFilter.add(key);
          }
      
          /**
           * 获取字典信息-穿透布隆过滤器
           *
           * @param key
           * @return
           */
          public String getDictInfoByBloomFilter(String key) {
              //布隆过滤器检查无效key直接返回空
              if (!bloomContains(key)) {
                  return null;
              }
              //1、从redis中获取值
              String dictValue = getFromRedis(key);
              if (dictValue == null) {
                  //当redis中不存在时访问数据库
                  dictValue = sysDictDbService.loadData(key);
                  if (dictValue != null) {
                      //从数据库中加载数据并放入redis
                      setValueToRedis(key, dictValue);
                      //添加到bitmap中
                      addKeyToBloomFilter(key);
                  }
              }
              return dictValue;
          }
      
Redis缓存击穿
  • 击穿概念

    缓存击穿是key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,频繁访问数据库,此和缓存雪崩有点类似,区别就是雪崩是不同的key集体同时过期,击穿则是单个key过期大量请求,解决方案和雪崩类似就不多阐述了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值