Redis 缓存击穿、缓存穿透、缓存雪崩

高并发流量,访问的这个数据是热点数据,请求的数据在 DB 中存在,但是 Redis 存的那一份已经过期,后端需要从 DB 从加载数据并写到 Redis。

总结起来就是:单一热点数据、高并发、数据失效。

https://cdn.xiaobinqt.cn/xiaobinqt.io/20220413/10c8c8557c8b41ea967c82617bef1814.png
缓存击穿

对于热点数据,我们不设置过期时间,这样就可以把请求都放在缓存中处理,充分把 Redis 高吞吐量性能利用起来。或者过期时间再加一个随机值。

设计缓存的过期时间时,使用公式:过期时间 = baes 时间 + 随机时间

即相同业务数据写缓存时,在基础过期时间之上,再加一个随机的过期时间,让数据在未来一段时间内慢慢过期,避免瞬时全部过期,对 DB 造成过大压力。

预先把热门数据提前存入 Redis 中,并设热门数据的过期时间超大值。

当发现缓存失效的时候,不是立即从数据库加载数据。

而是先获取分布式锁,获取锁成功才执行数据库查询和写数据到缓存的操作,获取锁失败,则说明当前有线程在执行数据库查询操作,当前线程睡眠一段时间再重试。这样只让一个请求去数据库读取数据。

数据库本就没有这个数据,请求直奔数据库,缓存系统形同虚设。

大量请求的 key 根本不存在于缓存中也不存在数据库,导致请求直接到了数据库上,根本没有经过缓存这一层,对数据库造成压力而影响正常服务。

https://cdn.xiaobinqt.cn/xiaobinqt.io/20220413/19352698c9c047d4a635c3e561b8293b.png
缓存穿透

最基本的首先就是做好参数校验,一些不合法的参数请求直接抛出异常信息返回给客户端。

如果缓存和数据库都查不到 某个 key 的数据就写一个到 Redis 中去并设置过期时间。这种方式可以解决请求的 key 变化不频繁的情况,如果黑客恶意攻击,每次构建不同的请求 key,会导致 Redis 中缓存大量无效的 key 。这种方案并不能从根本上解决此问题。如果非要用这种方式来解决穿透问题的话,应该尽量将无效的 key 的过期时间设置短一点比如 1 分钟。

布隆过滤器是一个非常神奇的数据结构,通过它我们可以非常方便地判断一个给定数据是否存在于海量数据中。我们需要的仅仅就是判断 key 是否合法。

具体是这样做的:把所有可能存在的请求的值都存放在布隆过滤器中,当用户请求过来,先判断用户发来的请求的值是否存在于布隆过滤器中。不存在的话,直接返回请求参数错误信息给客户端,存在的话再走其他的判断流程。

https://cdn.xiaobinqt.cn/xiaobinqt.io/20220407/c81922434bf5420c94e349e07980054b.png
布隆过滤器

需要注意的是布隆过滤器可能会存在误判的情况。

布隆过滤器说某个元素存在,小概率会误判。布隆过滤器说某个元素不在,这个元素一定不在

这是因为,当一个元素加入布隆过滤器中的时候,会进行如下操作:

  1. 使用布隆过滤器中的哈希函数对元素值进行计算,得到哈希值(有几个哈希函数得到几个哈希值)。
  2. 根据得到的哈希值,在数组中把对应下标的值置为 1。

当需要判断一个元素是否存在于布隆过滤器的时候,会进行如下操作:

  1. 对给定元素再次进行相同的哈希计算;
  2. 得到值之后判断位数组中的每个元素是否都为 1,如果值都为 1,那么说明这个值在布隆过滤器中,如果存在一个值不为 1,说明该元素不在布隆过滤器中。

然而,一定会出现这样一种情况:不同的字符串可能哈希出来的位置相同。我们可以适当增加位数组大小或者调整我们的哈希函数来降低概率。

缓存在同一时间大面积的失效,后面的请求都直接落到了数据库上,造成数据库短时间内承受大量请求。或是有一些被大量访问数据(热点缓存)在某一时刻大面积失效,导致对应的请求直接落到了数据库上。

而出现该原因主要有两种:

  • 大量热点数据同时过期,导致大量请求需要查询数据库并写到缓存。
  • Redis 故障宕机,缓存系统异常。

数据保存在缓存系统并设置了过期时间,但是由于在同时一刻,大量数据同时过期。系统就把请求全部打到数据库获取数据,并发量大的话就会导致数据库压力激增。

缓存雪崩是发生在大量数据同时失效的场景,而缓存击穿是在某个热点数据失效的场景,这是他们最大的区别

https://cdn.xiaobinqt.cn/xiaobinqt.io/20220413/12a33b5773ef4e36be2c71a94b93df11.png
缓存大量数据同时过期

要避免给大量的数据设置一样的过期时间,过期时间 = baes 时间+ 随机时间(较小的随机数,比如随机增加 1~5 分钟)。

这样一来,就不会导致同一时刻热点数据全部失效,同时过期时间差别也不会太大,既保证了相近时间失效,又能满足业务需求。

当访问的不是核心数据的时候,在查询的方法上加上接口限流保护。比如设置 10000 req/s。如果访问的是核心数据接口,缓存不存在允许从数据库中查询并设置到缓存中。这样的话,只有部分请求会发送到数据库,减少了压力。

限流,就是指,我们在业务系统的请求入口前端控制每秒进入系统的请求数,避免过多的请求被发送到数据库

https://cdn.xiaobinqt.cn/xiaobinqt.io/20220413/340401972fac48f391fa6906a9444eac.png
限流

一旦 Redis 故障或宕机,会导致大量请求打到数据库,从而发生缓存雪崩。对于 Redis 故障,主要有以下两种解决方案。

在业务系统中,针对高并发的使用服务熔断来有损提供服务从而保证系统的可用性。

服务熔断就是当从缓存获取数据发现异常,则直接返回错误数据给前端,防止所有流量打到数据库导致宕机。

服务熔断和限流属于在发生了缓存雪崩,如何降低雪崩对数据库造成的影响的方案。

缓存系统一定要构建一套 Redis 高可用集群,比如 Redis 哨兵集群 TODO 或者 Redis Cluster 集群 TODO,如果 Redis 的主节点故障宕机了,从节点还可以切换成为主节点,继续提供缓存服务,避免了由于缓存实例宕机而导致的缓存雪崩问题。

  • 缓存穿透指的是数据库本就没有这个数据,请求直奔数据库,缓存系统形同虚设。
  • 缓存击穿(失效)指的是数据库有数据,缓存本应该也有数据,但是缓存过期了,Redis 这层流量防护屏障被击穿了,请求直奔数据库。
  • 缓存雪崩指的是大量的热点数据无法在 Redis 缓存中处理(大面积热点数据缓存失效、Redis 宕机),流量全部打到数据库,导致数据库极大压力。