Redis的缓存穿透、缓存击穿、缓存雪崩
本文简单记录了Redis的缓存穿透、缓存击穿、缓存雪崩三大常见生产问题,及对应的解决方案。
推荐文章:
缓存穿透
简介
缓存穿透:简单来说就是大量请求的 key 是不合理的,缓存和数据库中都不存在,可用户仍源源不断的发起请求,导致每次请求都到数据库,从而压垮数据库。
举例:某黑客故意制造一些非法的 key 发起大量请求,导致大量请求落到数据库,结果数据库上也没有查到对应的数据。也就是说这些请求最终都落到了数据库上,对数据库造成了巨大的压力。

解决办法
业务层校验
- 用户发过来的请求,根据请求参数进行校验,对于明显错误的参数,直接拦截返回。
 - 如请求参数为主键自增id,那么对于请求小于0的id参数,可以直接返回错误请求。
 - 如传入的邮箱格式不对时直接返回错误消息给客户端。
 
不存在数据设置短过期时间
对于某个查询为空的数据,可以将这个空结果进行
Redis缓存,但设置很短的过期时间,命令:SET key value EX 30000。这种方式可解决请求 key 变化不频繁的情况,若黑客恶意攻击,每次构建不同的请求 key,会导致 Redis 缓存大量无效的 key。
1
2
3
4
5
6
7
8
9
10
11
12public Object getObjectInclNullById(Integer id) {
Object cacheValue = cache.get(id); // 从缓存中获取数据
if (cacheValue == null) { // 缓存为空
Object storageValue = storage.get(key);// 从数据库中获取
cache.set(key, storageValue);// 缓存空对象
if (storageValue == null) { // 如果存储数据为空,需要设置一个过期时间(300秒)
cache.expire(key, 60 * 5);// 必须设置过期时间,否则有被攻击的风险
}
return storageValue;
}
return cacheValue;
}
布隆过滤器
- 布隆过滤器是一种数据结构,利用极小的内存,可以判断大量的数据“一定不存在或者可能存在”。
 - 对于缓存击穿,把所有可能存在的请求的值都都哈希到一个足够大的布隆过滤器中,用户发送的请求会先被布隆过滤器拦截,一定不存在的数据就直接拦截返回请求参数错误信息给客户端了,存在的话才会走下面的流程从而避免下一步对数据库的压力。
 
接口限流
根据用户或者 IP 对接口进行限流,对于异常频繁的访问行为,还可以采取黑名单机制,例如将异常 IP 列入黑名单。
后面提到的缓存击穿和雪崩都可以配合接口限流来解决,毕竟这些问题的关键都是有很多请求落到了数据库上造成数据库压力过大。
限流的具体方案可参考文章:服务限流。
缓存击穿
简介
缓存击穿:请求的 key 对应的是 热点数据,该数据 存在于数据库中,但不存在于缓存中(通常是因为缓存中的那份数据已经过期)。Redis中一个热点key在失效的同时,大量的请求过来,从而会全部到达数据库,压垮数据库。
举例:秒杀进行过程中,缓存中的某个秒杀商品的数据突然过期,这就导致瞬时大量对该商品的请求直接落到数据库上,对数据库造成了巨大的压力。

解决办法
- 设置热点数据永不过期(不推荐)
- 对于某个需要频繁获取的信息,缓存在Redis中,并设置其永不过期。这种方式比较粗暴,对于某些业务场景是不适合的。
 
 - 提前预热(推荐):对热点数据提前预热,将其存入缓存中并设置合理过期时间,如秒杀场景下数据在秒杀结束之前不过期。
 - 定时更新
- 比如某热点数据的过期时间是1h,那么每到59minutes时,通过定时任务更新热点key,并重新设置其过期时间。
 
 - 互斥锁(常用)
- 在缓存失效后,通过设置互斥锁确保只有一个请求去查询数据库并更新缓存。
 - 在
Redis中根据key获得value为空时先锁上,再从数据库加载,加载完毕,释放锁。 - 若其他线程也在请求该key时,获取锁失败,则睡眠一段时间(如100ms)后重试。
 
 
缓存穿透和缓存击穿的区别
缓存穿透中,请求的 key 既不存在于缓存中,也不存在于数据库中。
缓存击穿中,请求的 key 对应的是 热点数据 ,该数据 存在于数据库中,但不存在于缓存中(通常是因为缓存中的那份数据已经过期)
缓存雪崩
简介
缓存雪崩:简单来说,Redis中缓存的数据大面积同时失效,或者Redis缓存服务宕机,导致大量请求直接到数据库,压垮数据库。
举个例子:缓存中的大量数据在同一时间过期,这个时候突然有大量的请求需要访问这些过期的数据。这就导致大量的请求直接落到数据库上,对数据库造成了巨大的压力。

解决办法
针对 Redis 服务不可用的情况:
- Redis 集群:采用 Redis 集群,以防止Redis集群单节点故障导致整个缓存服务不可用。Redis Cluster 和 Redis Sentinel 是两种最常用的 Redis 集群实现方案,详细介绍可以参考:Redis 集群。
 - 多级缓存:设置多级缓存,例如本地缓存+Redis 缓存的二级缓存组合,当 Redis 缓存出现问题时,还可以从本地缓存中获取到部分数据。
 
针对大量缓存同时失效的情况:
- 持久缓存策略(看情况):一般不推荐设置缓存永不过期,但对于某些关键性和变化不频繁的数据,可以考虑这种策略。
 - 设置有效期均匀分布
- 设置随机失效时间(可选):避免缓存设置相近的有效期,可为缓存设置随机的失效时间,比如在设置有效期时增加随机值;
 - 或者统一规划有效期,使得过期时间均匀分布。
 
 - 数据预热(推荐)
- 针对热点数据提前预热,将其存入缓存中并设置合理的过期时间,比如秒杀场景下的数据在秒杀结束之前不过期。
 - 对于即将来临的大量请求,可以提前走一遍系统,将数据提前缓存在Redis中,并设置不同的过期时间。
- 使用定时任务,比如 xxl-job,来定时触发缓存预热的逻辑,将数据库中的热点数据查询出来并存入缓存中。
 - 使用消息队列,比如 Kafka,来异步地进行缓存预热,将数据库中的热点数据的主键或者 ID 发送到消息队列中,然后由缓存服务消费消息队列中的数据,根据主键或者 ID 查询数据库并更新缓存。
 
 
 
缓存雪崩和缓存击穿的区别
缓存雪崩和缓存击穿比较像,但缓存雪崩导致的原因是缓存中的大量或者所有数据失效,缓存击穿导致的原因主要是某个热点数据不存在于缓存中(通常是因为缓存中的那份数据已经过期)。




