第九章: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 为什么不能当数据库?》

相关推荐
IT大白鼠4 天前
Redis 系列 · 第 01 篇——认知入门:Redis 是什么
redis·nosql
彧azz4 天前
Linux 环境下 Redis 学习总结:数据类型、持久化、锁、事务、主从与缓存问题
linux·redis·笔记·学习·面试
lbb 小魔仙4 天前
OpenClaw + cpolar 实战:远程 NAS、分享小游戏、RDP,再配置公网 AI 入口
数据库·人工智能·redis·oracle·prometheus
夕除4 天前
redis--018
redis
两锭子4 天前
Redis基础
redis
烟沙九洲4 天前
Redis 缓存穿透、缓存击穿、缓存雪崩
数据库·redis
字节探索4 天前
Redis 只会当缓存用?这 10 大实战场景,让你的系统快到飞起
redis·后端
于樱花森上飞舞4 天前
【Redis】哨兵详解
java·开发语言·数据库·redis
Rain的Java大神之路5 天前
如何快速上传10G文件
java·spring boot·redis·后端·mysql·spring cloud·面试
Wang's Blog5 天前
Java 接入Redis: 通用命令与键管理
java·服务器·redis