Redis学习笔记之缓存雪崩、击穿、穿透

December 19, 2021

缓存雪崩

表现为由于大量的应用请求无法在缓存中处理,导致数据库压力骤增

造成缓存雪崩的原因主要有两个:

  1. 大量缓存同时过期

    • 尽量避免为大批量缓存数据设置相同的过期时间,如确有这个需求,可为每个过期时间增加一点随机值(比如随机增加1~3分钟)

    • 如果已经发生了,可考虑服务降级

      服务降级指负载过大时,为了保证重要、基本、核心的服务能正常运行,将一些不重要的、不紧急的服务延迟使用暂停使用

      在缓存雪崩场景下,对于非核心数据可直接返回预定义值,不再查询缓存,等缓存恢复后再恢复相关服务。对于核心数据,仍可查询缓存或数据库,保留服务。

      扩展一下,服务降级的方式常见有以下几种:

      1. 延迟服务:可采用定时任务或mq延迟处理。比如可在凌晨流量较小的时候再处理,或提示用户多长时间内会处理完等等;
      2. 暂停服务:关闭一些非核心服务。比如在双十一时候,买东西都不允许修改收货地址,不允许退货退款等等,保证核心服务下单支付正常就好;
      3. 写降级:不需要实时写到数据库中,写到缓存中即可,由 mq 异步更新数据库,保证最终一致性即可;
      4. 读降级:针对一些对读实时性、一致性要求不高的场景,可降级为只查询缓存。
  2. redis宕机了

    • 事前预防:部署主从哨兵集群,主节点挂了,哨兵会把从节点提升为主节点。

    • 如果已经发生了,可考虑服务熔断或服务限流

      服务熔断可看做是服务降级的一种特殊情况,其初衷都差不多。

      区别在于服务降级是从整体负载考虑,从业务层面考虑,人为控制。而服务熔断是为了避免由于某个服务不可用而导致整条服务链路崩溃,当触发熔断条件时自动熔断,返回 fallback 方法,是框架层面的,自动熔断自动恢复


      关于服务限流,这篇文章讲的很好

      限流的实现方式





缓存击穿

指的是针对热点数据的请求,无法在缓存中处理,导致数据库压力骤增。

这种情况经常发生在热点数据过期失效时,解决办法就是对于热点数据,不要设置过期时间





缓存穿透

指的是在缓存和数据库中都没有数据,缓存成了摆设,如果持续有大量请求涌入,会同时给缓存和数据库带来巨大压力。

主要原因有两个:

  1. 误操作,删除了缓存和数据库中的数据
  2. 恶意攻击,专门访问不存在的数据

解决办法有三个:

  1. 缓存空值或缺省值,其实相当于服务降级

  2. 使用布隆过滤器判断数据存不存在,如果不存在,就不用再去数据库查询了

    布隆过滤器是基于位图的一种改进。

    位图是一种用来判断存在性的很好用的数据结构。比如这么一个问题:有1千万个整数,范围是1到1亿,如何快速的判断某个整数是否存在这1千万个整数中?

    可初始化一个长度为1亿、数据类型为 bit 的数组,每个数对应下标位置,比如5对应到array[5]=1。要判断整数 x 是否在这1千万个整数中,只要判断 array[x] 是否等于 1 即可。基于数组随机访问的特性,查找效率也比较高。而且相较于其他数据结构所需空间更少,1亿个 bit 只需 12M,如果采用散列表则需要 1亿 * 4byte = 40M 左右的空间。

    可看到位图存储空间和范围相关,如果上述问题的范围是1到10亿,则需120M存储空间。布隆过滤器就是为了解决这个问题的进一步优化。

    还以上面的问题为例,范围扩大到1至10亿。布隆空滤器的做法是还是申请一个长度为1亿、数据类型为 bit 的数组,定义 n 个哈希函数,对每个整数进行n次哈希运算得到n个哈希值,将这n个哈希值对于下标的位置都设为 1 。进行多个哈希运算的目的是为了尽可能的降低哈希冲突的概率。

    判断的时候如果这n个下标对应的位置有一个不为1,则说明该数据肯定不存在。但如果都为1,也不一定说明该数据肯定存在,因为这几个位置对应的1可能是由其他数据进行哈希运算后填进去的,比如2、4、6 这3个位置都为1,有可能是由其他3个整数分别填进去的。可通过调整哈希函数的个数、位图大小与要存储的数字之间的比例,来降低误判的概率。

    所以,布隆过滤器的特点是判定为不存在的肯定不存在,而判定为存在的情况可能有误判(判定为存在,实则不存在)。通过多个哈希函数来共同决定存储位置,这种方法其实误判的概率已经非常低了。所以布隆过滤器特别适合这种场景:判定不存在,或者对存在误判有一定容忍度的应用场景。

    具体实现有 Java 的 BitSet(位图)、Redis 的 BitMap(位图)、Guava 的 BloomFilter(布隆过滤器)。

  3. 在前端入口进行合法性检测,把恶意请求(比如请求参数不合理、请求参数是非法值、请求字段不存在等)过滤掉