Redis-缓存击穿,雪崩,穿透

本文最后更新于:1 年前

[TOC]

概述

首先,雪崩的概念比较容易记,就是很多个Key同时过期才会雪崩,”缓存雪崩的时候没有一个Key是无辜的”。

至于穿透和击穿,区别在于穿透是”透”,什么叫透呢,那就是不仅缓存被击穿了,数据库也被击穿了,这种才叫透。所以,这种缓存和数据库中都没有的情况叫做”缓存穿透”。

击穿是 redis 没有,直接打到 db。

缓存穿透

访问一个缓存和数据库都不存在的 key,此时会直接打到数据库上,并且查不到数据,没法写缓存,所以下一次同样会打到数据库上。此时,缓存起不到作用,请求每次都会走到数据库,流量大时数据库可能会被打挂。此时缓存就好像被“穿透”了一样,起不到任何作用。

解决方案

1)接口校验。在正常业务流程中可能会存在少量访问不存在 key 的情况,但是一般不会出现大量的情况,所以这种场景最大的可能性是遭受了非法攻击。可以在最外层先做一层校验:用户鉴权、数据合法性校验等,例如商品查询中,商品的 ID 是正整数,则可以直接对非正整数直接过滤等等。

2)缓存空值。当访问缓存和 DB 都没有查询到值时,可以将空值写进缓存,但是设置较短的过期时间,该时间需要根据产品业务特性来设置。

3)布隆过滤器。使用布隆过滤器存储所有可能访问的 key,不存在的 key 直接被过滤,存在的 key 则再进一步查询缓存和数据库

布隆过滤器

布隆过滤器的特点是判断不存在的,则一定不存在判断存在的,大概率存在,但也有小概率不存在。并且这个概率是可控的,我们可以让这个概率变小或者变高,取决于用户本身的需求。

布隆过滤器由一个 bitSet 和 一组 Hash 函数(算法)组成,是一种空间效率极高的概率型算法和数据结构,主要用来判断一个元素是否在海量数据集合中存在

在初始化时,bitSet 的每一位被初始化为 0,同时会定义 Hash 函数,例如有 3 组 Hash 函数:hash1、hash2、hash3。

写入流程

当我们要写入一个值时,过程如下,以“x”为例:
1)首先将“x”跟 3 组 Hash 函数分别计算,得到 bitSet 的下标为:1、7、10。
2)将 bitSet 的这 3 个下标标记为 1。

假设我们还有另外两个值:java 和 q,按上面的流程跟 3 组 Hash 函数分别计算,结果如下:
java:Hash 函数计算 bitSet 下标为:1、7、11
q 函数计算 bitSet 下标为:4、10、11

查询流程

当我们要查询一个值时,过程如下,同样以“x”为例::
1)首先将“x”跟 3 组 Hash 函数分别计算,得到 bitSet 的下标为:1、7、10。
2)查看 bitSet 的这 3 个下标是否都为 1,如果这 3 个下标不都为 1,则说明该值必然不存在,如果这 3 个下标都为 1,则只能说明可能存在,并不能说明一定存在。

image-20210804194430890

其根本原因是,不同的值在跟 Hash 函数计算后,可能会得到相同的下标,所以某个值的标记位,可能会被其他值给标上了。
这也是为啥布隆过滤器只能判断某个值可能存在,无法判断必然存在的原因。但是反过来,如果该值根据 Hash 函数计算的标记位没有全部都为 1,那么则说明必然不存在,这个是肯定的。

降低这种误判率的思路也比较简单:
一个是加大 bitSet 的长度,这样不同的值出现“冲突”的概率就降低了,从而误判率也降低。
提升 Hash 函数的个数,Hash 函数越多,每个值对应的 bit 越多,从而误判率也降低。

缓存击穿

某一个热点 key,在缓存过期的一瞬间,同时有大量的请求打进来,由于此时缓存过期了,所以请求最终都会走到数据库,造成瞬时数据库请求量大、压力骤增,甚至可能打垮数据库。

解决方案

预先准备,以电商为例,每个商家根据店铺等级,指定若干款主打商品,在购物节期间,加大此类信息 key 的过期时长。

现场调整
监控访问量,对自然流量激增的数据延长过期时间或设置为永久性 key

后台刷新数据
启动定时任务,高峰期来临之前,刷新数据有效期,确保不丢失

二级缓存
设置不同的失效时间,保障不会被同时淘汰就行

加锁
分布式锁,防止被击穿,但是要注意也是性能瓶颈,慎重!

总结
缓存击穿就是单个高热数据过期的瞬间,数据访问量较大,未命中 redis 后,发起了大量对同一数据的数据库访问,导致对数据库服务器造成压力。应对策略应该在业务数据分析与预防方面进行,配合运行监控测试与即时调整策略,毕竟单个 key 的过期监控难度较高,配合雪崩处理策略即可。

缓存雪崩

大量的热点 key 设置了相同的过期时间,导在缓存在同一时刻全部失效,造成瞬时数据库请求量大、压力
骤增,引起雪崩,甚至导致数据库被打挂。
缓存雪崩其实有点像“升级版的缓存击穿”,缓存击穿是一个热点 key,缓存雪崩是一组热点 key。

解决方案

过期时间打散。既然是大量缓存集中失效,那最容易想到就是让他们不集中生效。可以给缓存的过期时间时加上一个随机值时间,使得每个 key 的过期时间分布开来,不会集中在同一时刻失效。

热点数据不过期。该方式和缓存击穿一样,也是要着重考虑刷新的时间间隔和数据异常如何处理的情况。

加互斥锁。该方式和缓存击穿一样,按 key 维度加锁,对于同一个 key,只允许一个线程去计算,其他线程原
地阻塞等待第一个线程的计算结果,然后直接走缓存即可

热 key

如果在同一个时间点上,Redis中的同一个key被大量访问,就会导致流量过于集中,使得很多物理资源无法支撑,如网络带宽、物理存储空间、数据库连接等。

这也是为什么某某明星官宣之后,微博上面就会出现宕机的情况。有时候这种宕机发生后,其他功能都是可以使用的,只是和这个热点有关的内容会无法访问,这其实就和热点数据有关系了。

对于热key的处理,主要在于事前预测和事中解决。

对于事前预测就是根据一些根据经验,提前的识别出可能成为热key的Key,比如大促秒杀活动等。

在事中解决方面,主要可以考虑,热点key拆分、多级缓存、热key备份、限流等方案来解决。

大 key

Big Key是Redis中存储了大量数据的Key,不要误以为big key只是表示Key的值很大,他还包括这个Key对应的value占用空间很多的情况,通常在String、list、hash、set、zset等类型中出现的问题比较多。其中String类型就是字符串的值比较大,而其他几个类型就是其中元素过多的情况。

  • 对于 String 类型的 Value 值,值超过 5MB(腾讯云定义是10M,阿里云定义是5M,我认为5M合适一点)。
  • 对于 Set 类型的 Value 值,含有的成员数量为 10000 个(成员数量多)。
  • 对于 List 类型的 Value 值,含有的成员数量为 10000 个(成员数量多)。
  • 对于 Hash 格式的 Value 值,含有的成员数量 1000 个,但所有成员变量的总 Value 值大小为 100MB(成员总的体积过大)。

命令 -bigkeys 可识别

1、有选择地删除Big Key:针对Big Key,我们可以针对一些访问频率低的进行有选择性的删除,删除Big Key来优化内存占用。

2、除了手动删除以外,还可以通过合理的设置缓存TTL,避免过期缓存不及时删除而增大key大小。

3、Big Key的主要问题就是Big,所以我们可以想办法解决big的问题,那就是拆分呗,把big的key拆分开:
a、在业务代码中,将一个big key有意的进行拆分,比如根据日期或者用户尾号之类的进行拆分。使用小键替代大键可以有效减小存储空间,从而避免影响系统性能
b、使用Cluster集群模式,以将大 key 分散到不同服务器上,以加快响应速度。

4、部分迁移:将大键存放在单独的数据库中,从而实现对大键的部分迁移