第九章:Redis 为什么会发生缓存穿透、击穿、雪崩?

Redis 为什么会发生缓存穿透、击穿、雪崩?

上一篇我们聊了《Redis 为什么不会每次修改都写磁盘?》,了解了 Redis 如何通过 AOF 刷盘策略,在数据安全和性能之间寻找平衡。

Redis 通过内存提供高速访问能力,通过持久化机制保证数据可以恢复。

但是在真实业务中,Redis 最大的挑战并不是"数据能不能保存"。

而是:当大量请求同时访问 Redis 时,系统还能不能稳定运行。

例如一个商品详情接口:

用户请求商品信息

查询 Redis

存在

直接返回

不存在

查询 MySQL

写入 Redis

看起来非常简单。

但是,当请求量突然增加时,会出现三个非常经典的问题:

缓存穿透

缓存击穿

缓存雪崩

很多面试会问:

Redis 缓存三大问题是什么?

但真正需要理解的是:

为什么这些问题会发生?

因为它们背后反映的是缓存系统设计中的三个核心矛盾:

请求的数据根本不存在怎么办?

热点数据突然失效怎么办?

大量缓存同时失效怎么办?

Redis 为什么需要缓存?

在理解三个问题之前,需要先明确一个基础:

为什么业务系统需要 Redis?

假设一个商品查询:

select *

from product

where id = 1001;

如果每一次请求都访问 MySQL:

用户请求

MySQL

返回结果

当访问量增加时:

100 个请求

MySQL

可能还能处理。

但是:

10 万请求

MySQL

数据库压力会快速增加。

因为数据库不仅需要查询数据,还需要:

SQL 解析

索引查找

数据页读取

网络传输

事务处理

所以系统通常会增加 Redis:

第一次请求

查询 MySQL

写入 Redis

后续请求

Redis

直接返回

Redis 承担了大量热点访问,但是缓存并不是万能的。

它引入了新的问题。什么是缓存穿透?

缓存穿透指的是:

请求的数据,在缓存和数据库中都不存在,但是请求不断访问系统。

例如:

接口:GET /user/999999

Redis:不存在

MySQL:不存在

但是攻击者不断请求:

/user/999999

/user/999998

/user/999997

...

整个流程变成:

用户请求

Redis查询

没有数据

查询MySQL

没有数据

返回不存在

问题在哪里?

Redis 没有缓存这个"不存在"。所以每一次请求都会穿过 Redis,直接访问数据库。

这就是缓存穿透。

为什么缓存穿透很危险?因为正常缓存流程:

请求

Redis

命中

返回

数据库压力很小。但是缓存穿透:

大量请求

Redis

全部未命中

MySQL

Redis 完全失去了保护数据库的作用。

如果请求量足够大:

大量不存在的数据

绕过缓存

打到数据库

数据库压力过高

服务异常

所以缓存穿透本质上是:缓存无法拦截无效请求。

如何解决缓存穿透?常见方案有三个。

方案一:缓存空对象

最简单的方法:即使数据库没有数据,也把不存在的结果缓存起来。

例如:

查询:user:999999

数据库:null

Redis 保存:user:999999 = null

下一次请求:

请求

Redis

发现是空值

直接返回不存在

流程变成:

第一次请求

Redis没有

查询数据库

保存空结果

第二次请求

Redis命中空结果

直接返回

这样可以避免大量相同不存在请求访问数据库。

但是有一个问题:

如果缓存大量不存在的数据:

user:100001

user:100002

user:100003

会占用 Redis 空间。

所以通常会设置较短过期时间。

例如:

空对象缓存 5 分钟

方案二:布隆过滤器

另一种方式是:

在 Redis 前增加一个过滤层。

例如:

请求

布隆过滤器

判断数据是否存在

不存在

直接返回

布隆过滤器特点:它可以快速判断:"这个数据一定不存在。"

例如:

商品 ID:

1001

1002

1003

加入布隆过滤器。

请求:9999

过滤器判断:不存在直接拒绝。

这样请求不会进入 Redis 和数据库。但是布隆过滤器有一个特点:可能误判存在。

也就是说:不存在的数据,一定能判断不存在。存在的数据,可能判断存在。

所以它适合作为第一层过滤。

什么是缓存击穿?

缓存击穿和缓存穿透很容易混淆。

缓存穿透:请求的数据根本不存在。

缓存击穿:请求的数据存在,但是突然失效。

例如:

某个热点商品:product:1001

平时:Redis

product:1001

直接返回

但是某一天:缓存过期。

此时大量用户同时访问:

100 万请求

Redis全部未命中

同时查询MySQL

流程:

请求1

|

请求2

|

请求3

|

请求N

Redis缓存失效

同时访问数据库

这就是缓存击穿。

为什么缓存击穿比普通缓存失效更危险?

因为它通常发生在:

热点数据。

普通数据:一天访问几十次,缓存失效影响不大。

热点数据:一分钟几十万次

一旦失效:大量请求同时访问数据库。数据库瞬间承受缓存原本承担的压力。

所以缓存击穿的核心问题:不是数据不存在。而是大量请求同时发现缓存不存在。

如何解决缓存击穿?

方案一:互斥锁

最常见方案。第一个请求发现缓存不存在:

请求1

Redis没有数据

获取锁

查询数据库

写入缓存

其他请求:

请求2

发现锁存在

等待

最终:一个请求查询数据库,其他请求共享结果

流程:

请求1

|

获取锁

|

查询数据库

|

写入Redis

请求2/3/4

|

等待结果

这样可以避免数据库被瞬间打爆。

方案二:热点数据永不过期

对于一些极端热点数据:

例如:

首页配置

热门商品

活动信息

可以不设置过期时间。而是在后台主动更新。

例如:

定时任务

刷新热点数据

更新Redis

这样不会出现突然失效。

什么是缓存雪崩?缓存雪崩比缓存击穿更严重。

它指的是:

大量缓存数据在同一时间失效。

例如:

系统启动时:

10万个商品

同时写入Redis

过期时间都是30分钟

30分钟之后大量Key同时过期

结果:

请求

Redis大量未命中

MySQL压力暴涨

整个系统像发生雪崩一样。

所以叫:

缓存雪崩。为什么会出现缓存雪崩?

通常有两个原因。

原因一:大量 Key 同时过期

例如:

商品缓存

过期时间30分钟

如果所有数据都是:expire(key, 1800)

那么它们可能同时失效。

原因二:Redis 故障

除了 Key 过期:Redis 本身也可能出现问题。

例如:

Redis宕机

所有请求访问数据库

数据库压力暴涨

这也是缓存雪崩的一种。

如何解决缓存雪崩?

方案一:过期时间增加随机值

例如:

不要30分钟

而是30分钟 + 随机时间

例如:

30分钟

0~10分钟随机值

这样:

商品1

30分钟过期

商品2

35分钟过期

商品3

38分钟过期

避免大量 Key 同时失效。

方案二:缓存预热

系统启动之前提前加载热点数据。

例如:

服务启动

加载热门商品

写入Redis

用户访问避免系统刚上线时大量请求打数据库。

方案三:增加降级机制

当 Redis 或数据库压力过高:系统需要保护自己。

例如:

Redis异常

返回默认数据

限制请求

避免整个服务不可用。

缓存穿透、击穿、雪崩有什么区别?

很多人容易混淆。

可以这样理解:

问题 原因 影响

缓存穿透 数据不存在 请求绕过缓存访问数据库
缓存击穿 一个热点 Key 失效 大量请求访问同一个数据
缓存雪崩 大量 Key 同时失效 大量请求冲击数据库

简单记:

穿透:没有这个数据

击穿:一个热点数据没了

雪崩:大量数据一起没了

Redis 为什么一定会遇到这些问题?

因为缓存系统本质上是在做一件事情:

用一个高速但不完全可靠的存储,保护一个慢速但可靠的存储。

Redis 越成功:访问量越大。缓存承担的压力越大。那么当缓存失效时,产生的影响也越大。所以缓存问题不是 Redis 的缺陷。而是所有缓存系统都会面对的问题。

真正优秀的缓存设计,不是保证缓存永远有效。

而是在缓存失效时:

不让无效请求击穿系统

不让热点数据拖垮数据库

不让大量失效造成雪崩

这也是 Redis 从一个简单的缓存工具,变成大型互联网系统基础组件的重要原因。

上一篇:《Redis 为什么不会每次修改都写磁盘?》

下一篇:《Redis 为什么不能当数据库?》

相关推荐
Bug收容所8 小时前
12305项目学习day5
java·spring boot·redis·mysql·spring·rocketmq
智码看视界8 小时前
Day33-数据层 × 中间件AI化篇:Redis缓存经典问题-击穿、穿透、雪崩的终极解决方案
数据库·redis·缓存·中间件·穿透·雪崩·击穿
海兰1 天前
【高速缓存】RedisVL 存储类型选择指南:Hash 与 JSON
人工智能·redis·算法·缓存·json·哈希算法
Devin~Y1 天前
互联网大厂 Java 面试实录:Spring Boot、MyBatis、Redis、Kafka、Spring Security、RAG 与 MCP 全链路问答
java·redis·kafka·mybatis·spring security·spring mvc·sprint boot
海兰1 天前
【高速缓存】RedisVL 索引迁移指南:安全演进索引结构
前端·数据库·redis·安全·缓存·bootstrap
海兰1 天前
【高速缓存】 RedisVL MCP 运行指南(下)
人工智能·redis·哈希算法·高速缓存
hold?fish:palm2 天前
kv存储主从复制的设计与实现
c++·redis·后端
不如语冰2 天前
AI大模型入门-Python进阶-上下文管理与with语句
开发语言·数据结构·数据库·人工智能·pytorch·redis·python
程序员天天困2 天前
Java 项目用 Single-flight 一招终结 AI 接口重复调用
redis·后端