缓存雪崩(缓存大批量失效或者宕机)

概念

指在某一个时间段,缓存集中过期失效,或 Redis 宕机,导致针对这批数据的查询都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰。于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况。

其实缓存集中过期,倒不是最致命得到,比较致命的是 Redis 发生节点宕机或断网。因为缓存集中过期后,数据库压力增大,但是随着缓存的创建,压力也会逐渐变小。但是Redis 服务节点宕机,对数据库服务器造成的压力是不可预知的,很有可能是持续压力而最终造成数据库宕机

简单来说:

对于系统A,假设每天高峰期每秒5000个请求,本来缓存在高峰期可以抗住每秒4000个请求,但是缓存机器意外发生了全盘宕机。缓存挂了,此时1秒500个请求全部落到了数据库,数据库必然扛不住,它会报一下警,然后就挂了。此时,如果没有采用什么特别的方案来处理这个故障,DBA 很着急,重启数据库,但是数据库立马又被新的流量给打死了。

redis掉电 redis一段时间就挂掉_redis掉电

解决方案

方案一:配置 Redis 集群

通过配置 Redis 集群,提升高可用性,那么即使挂掉几个 Redis 节点,集群内的其他 Redis 节点依然可以继续对外提供 服务。

方案二:限流降级

缓存失效后,通过加锁或队列来控制读取数据库且写入缓存的线程数量。

方案三:数据预热分散过期时间

在正式部署之前,先把可能被高频访问的数据预先访问一遍,这样大部分热点数据就加载到缓存中了,并且通过设置不 同的过期时间,让缓存失效的时间尽量均匀,防止同一时刻大批量缓存失效。

缓存雪崩的事前事中事后的解决方案如下:

事前:Redis 高可用,主从+哨兵,Redis cluster,避免全盘崩溃。

事中:本地 ehcache 缓存 + hystrix 限流&降级,避免 MySQL 被打死。

事后:Redis 持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。

redis掉电 redis一段时间就挂掉_数据库_02

用户发送一个请求,系统 A 收到请求后,先查本地 ehcache 缓存,如果没查到再查 Redis。如果 ehcache 和 Redis 都没有,再查数据库,将数据库中的结果,写入 ehcache 和 Redis 中。

限流组件,可以设置每秒的请求,有多少能通过组件,剩余的未通过的请求,怎么办?走降级!可 以返回一些默认的值,或者友情提示,或者空值。

好处:

数据库绝对不会死,限流组件确保了每秒只有多少个请求能通过。

只要数据库不死,就是说,对用户来说,2/5 的请求都是可以被处理的。

只要有 2/5 的请求可以被处理,就意味着你的系统没死,对用户来说,可能就是点击几次刷不出来 页面,但是多点几次,就可以刷出来了。

缓存穿透(查不到数据)

概述:

当用户想要查询一个数据,发现 Redis 中不存在,也就是所谓的缓存没有命中,于是这个数据请求就会打到数据库中。结果数据库中也不存在这条数据,那么结果就是什么都没查询出来。那么当用户很多时候的查询,缓存中没有数据,请求直接打到数据库中,这样就会给数据库造成很大的压力,缓存的作用也就近于失效了,那么这种情况就叫缓存穿透。

简单解释:

对于系统A,假设一秒 5000 个请求,结果其中 4000 个请求是黑客发出的恶意攻击 。

黑客发出的那 4000 个攻击,缓存中查不到,每次你去数据库里查,也查不到。

举个栗子。数据库 id 是从 1 开始的,结果黑客发过来的请求 id 全部都是负数。这样的话,缓存中 不会有,请求每次都“绕过缓存”,直接查询数据库。这种恶意攻击场景的缓存穿透就会直接把数据库给打死。

redis掉电 redis一段时间就挂掉_缓存_03

解决方案:

方案一:保存空值

当数据库中也查不到数据时,那么将返回的空对象也缓存起来,同时设置一个过期时间,之后再访问这个数据将会从缓存中获取,从而起到保护数据库的作用。

例如:查询 userId=100 的用户信息(key=[userId],value=[用户 json]),那么如果缓存和 DB 中都不存在,则在缓存 中保存一条 key=100,value=""的数据,那么用户再查询 userId=100 的时候,就直接可以返回空了。不需要查询 DB。

方案二:布鲁过滤器

步骤 1:将数据库所有的数据加载到布隆过滤器。

步骤 2:当有请求来的时候先去布隆过滤器查询,判断查询的数据是否存在。

步骤 3:如果 Bloom Filter 判断数据不存在,那么直接返回空给客户端。

步骤 4:如果 Bloom Filter 判断数据存在,那么则查询缓存或 DB。

步骤 5:将 DB 中查询的结果返回给客户端(并且缓存到 Redis 中)。

缓存击穿(高并发查询某数据,且缓存过期)

概念:

指一个非常热点的key,在不停的高并发请求着,那么当这个key在缓存中失效的一瞬间,持续对这个 key 的高并发就击穿了缓存,直接请求到了数据库,就像在一个屏障上凿开了一个洞。

当热点 key 过期失效的一瞬间,高并发突然融入,会对数据库突然造成巨大的压力,严重的情况甚至会造成数据库宕机、

解决方案:

方案一:设置热点数据永不过期

从缓存层面来看,没有设置过期时间,所有不会出现热点 key 过期后所产生的缓存击穿问题。

方案二:加互斥锁(基于Redis、 zookeeper 等分布式中间件的分布式互斥锁)

使用分布式锁:当缓存数据过期后,保证对每个热点 key 同时只有一个线程去查询后端服务,并将热点数据添加到缓存。

方案三:

若缓存的数据更新频繁或者在缓存刷新的流程耗时较长的情况下,可以利用定时线程在缓存过期前 主动地重新构建缓存或者延后缓存的过期时间,以保证所有的请求能一直访问到对应的缓存。

缓存击穿 和 缓存穿透 这两者的区别:

  1. 缓存击穿重点在“击” 就是某个或者是几个热点 key 穿透了缓存层
  2. 缓存穿透重点在“透”:大量的请求绕过了缓存层

所有的请求能一直访问到对应的缓存。

缓存击穿 和 缓存穿透 这两者的区别:

  1. 缓存击穿重点在“击” 就是某个或者是几个热点 key 穿透了缓存层
  2. 缓存穿透重点在“透”:大量的请求绕过了缓存层