Redis 缓存穿透、击穿和雪崩有什么区别?怎么解决?
简化版
缓存穿透是查不存在的数据,请求绕过缓存打到数据库;缓存击穿是热点 key 过期,大量请求瞬间打到数据库;缓存雪崩是大量 key 同时失效或 Redis 故障,导致数据库被流量压垮。对应解决方案是缓存空值/布隆过滤器、互斥锁或逻辑过期、过期时间随机化和多级缓存/限流降级。
详细版
三者区别:
| 问题 | 典型表现 | 核心原因 | 常见方案 |
|---|---|---|---|
| 缓存穿透 | 不存在的数据反复查库 | 缓存和数据库都没有 | 缓存空值、布隆过滤器、参数校验 |
| 缓存击穿 | 单个热点 key 过期瞬间查库 | 热点 key 失效 | 互斥重建、逻辑过期、热点不过期 |
| 缓存雪崩 | 大量请求同时查库 | 大批 key 同时失效或 Redis 不可用 | TTL 随机、限流降级、多级缓存、高可用 |
面试回答时要先定义清楚“穿透、击穿、雪崩”的对象范围:穿透是不存在数据,击穿是单个热点 key,雪崩是大面积失效或缓存层不可用。
完整版教学
一、缓存穿透:请求查的是不存在的数据
比如用户不断请求 id=-1 或数据库中不存在的商品 ID。缓存里没有,数据库里也没有,于是每次都绕过缓存打到数据库。
解决方案:
- 参数校验:明显非法的 ID 直接拦截;
- 缓存空值:数据库查不到,也把空结果缓存一小段时间;
- 布隆过滤器:提前判断某个 ID 是否可能存在,不存在就不查库。
缓存空值要设置较短 TTL,避免数据库后来新增数据后长时间查不到。
用数字看,攻击者每秒请求 2 万个随机商品 ID,其中 99% 数据库都不存在。如果每次都查库,数据库会被无意义请求拖垮;缓存空值后,同一个不存在 ID 在短 TTL 内会被 Redis 拦住;布隆过滤器则能在请求进入缓存和数据库前先判断“这个 ID 一定不存在”。穿透治理重点是挡住不存在数据的重复回源。
穿透请求路径:
请求 id=999999999
-> Redis miss
-> DB miss
-> 不做处理:下次同样路径继续打 DB
-> 缓存空值/布隆过滤器:下次提前拦截
二、缓存击穿:热点 key 过期的一瞬间
击穿针对的是一个或少数热点 key。平时请求都被缓存挡住,突然这个 key 过期,海量请求同时发现缓存没有,于是一起查数据库。
解决方案:
- 互斥锁重建:只有一个线程查库并回填缓存,其他线程等待或返回旧值;
- 逻辑过期:缓存值里存过期时间,发现逻辑过期后异步重建,先返回旧数据;
- 热点 key 不设置物理过期:通过后台任务刷新。
强一致场景要谨慎返回旧值,不能为了抗压牺牲业务正确性。
假设一个热点商品详情平时每秒 1 万次读取,缓存突然过期。如果 1 秒内有 1 万个请求同时查库并重建缓存,数据库会承受原本 Redis 扛住的全部流量。互斥重建的思路是只允许 1 个请求查库,其余请求等待、快速失败或返回旧值;逻辑过期的思路是缓存物理上不删除,发现逻辑过期后异步刷新。
| 方案 | 优点 | 代价 |
|---|---|---|
| 互斥锁重建 | 数据较新,回源少 | 等待和锁超时处理复杂 |
| 逻辑过期 | 高可用,热点不突然消失 | 可能短暂返回旧值 |
| 热点不过期 | 避免过期击穿 | 需要后台刷新和兜底 |
记忆钩子:击穿是“一个热点 key 门突然开了”,治理目标是别让所有请求同时冲到数据库。
三、缓存雪崩:缓存层大面积失效
雪崩通常有两类:
- 大量 key 设置了相同 TTL,在同一时间集中失效;
- Redis 故障或网络异常,缓存整体不可用。
解决方案:
- TTL 加随机值,避免同一时间失效;
- Redis 做主从、哨兵或集群;
- 本地缓存做兜底;
- 限流、熔断、降级保护数据库;
- 热点数据预热。
雪崩的处理重点是系统保护,而不只是某个 key 的重建。
例如 100 万个商品 key 都设置为整点过期,到了 00:00 大量请求同时 miss,数据库流量会瞬间上升。TTL 随机化可以把 3600s 改成 3600s + random(0,600s),让过期时间分散在 10 分钟窗口内。若 Redis 整体故障,则需要高可用、多级缓存、限流降级来保护数据库,而不是只讨论单个 key 回填。
四、布隆过滤器不是万能的
布隆过滤器可以判断“某个值一定不存在或可能存在”。它有误判率:可能把不存在判断成可能存在,但不会把真实存在判断成一定不存在。
它适合抵挡大量非法 ID 查询,但要考虑数据新增时同步更新布隆过滤器,以及误判导致的少量查库。
布隆过滤器的回答要把“误判方向”说准。它可能把不存在的 ID 判断为可能存在,于是仍会查 Redis 或数据库;但不会把已加入过滤器的真实 ID 判断为一定不存在。新增数据时如果没有及时写入布隆过滤器,可能出现刚创建的数据被挡掉,因此新增链路要同步更新过滤器,或在业务上允许短暂延迟。
BloomFilter 判断:
不存在 -> 一定不存在:直接拦截
可能存在 -> 继续查缓存/数据库
五、工程上要组合拳
真实系统里三类问题可能同时出现。比如大促开始前热点数据没有预热,缓存 TTL 又集中到期,既有击穿也有雪崩风险。
比较稳的组合是:参数校验 + 空值缓存 + 热点 key 保护 + TTL 随机 + 限流降级 + Redis 高可用。
大促前可以提前预热热点商品、给热点 key 使用逻辑过期、给普通 key 设置随机 TTL、对不存在 ID 做布隆过滤器拦截,同时在网关或服务层限流。这样即使某一层失效,后面还有保护。缓存治理不应只靠 Redis 本身,还要结合入口校验、应用本地缓存、数据库保护和监控告警。
六、三者对比时要抓对象范围
面试中最容易混的是击穿和雪崩。击穿是少数热点 key 过期导致大量请求打库;雪崩是大量 key 同时失效或缓存层整体不可用。穿透则完全是另一个维度:查的是不存在数据。先把对象范围说清,再讲方案,答案就稳。
| 问题 | 对象范围 | 请求是否查到 DB 数据 | 关键词 |
|---|---|---|---|
| 穿透 | 不存在 key | DB 也没有 | 空值、布隆过滤器 |
| 击穿 | 单个热点 key | DB 有 | 互斥重建、逻辑过期 |
| 雪崩 | 大量 key 或缓存层 | DB 可能有大量数据 | TTL 随机、高可用、限流 |
七、常见误区与追问
- 误区:穿透、击穿、雪崩都是缓存过期。 穿透是查不存在数据,击穿是热点 key 过期,雪崩是大面积失效或缓存整体不可用。
- 误区:缓存空值 TTL 可以设置很长。 空值 TTL 过长会影响后续新增数据可见性,通常要短 TTL 并结合业务更新删除。
- 误区:布隆过滤器能保证 100% 准确。 布隆过滤器有误判率,只能保证已加入元素不会被判断为一定不存在。
- 误区:击穿用互斥锁后就没有风险。 锁超时、等待堆积、重建失败都要处理,热点高可用场景常配合逻辑过期。
- 追问:Redis 整体不可用属于哪类问题? 更接近雪崩,因为大量请求同时绕过缓存打到数据库,需要高可用、限流、降级和本地缓存保护。
- 追问:热点 key 逻辑过期为什么可能返回旧值? 因为物理缓存仍保留旧数据,发现逻辑过期后异步刷新,刷新完成前可能短暂返回旧值。
八、加强记忆
穿透、击穿、雪崩要按对象范围记:穿透是查不存在数据,缓存和数据库都没有;击穿是单个热点 key 过期,大量请求同时回源;雪崩是大量 key 同时失效或 Redis 整体不可用。穿透靠参数校验、空值缓存、布隆过滤器;击穿靠互斥重建、逻辑过期、热点预热;雪崩靠 TTL 随机、高可用、多级缓存、限流和降级。真正线上要打组合拳,而不是拿一个方案包治三类问题。