996 字
约 3 分钟
7
S12-J01 · 缓存穿透、击穿、雪崩
适用读者:分不清这三个词、或只会背定义的人。
前置:S12-B05。
覆盖旧文:879(110) 2671(108) 1840(79) = 297 点。
上一篇:S12-B05 · 缓存入门与 Spring Boot 整合
下一篇:S12-J02 · 缓存与数据库一致性。
1. 一句话结论
三个词的区别只在"缓存为什么没挡住":穿透=查的根本不存在;击穿=热点 key 到期;雪崩=大量 key 同时到期。
| 问题 | 缓存状态 | 请求特征 | 后果 |
|---|---|---|---|
| 穿透 | 缓存和 DB 都没有 | 恶意/无效 key(如 id=-1) |
每次都打 DB |
| 击穿 | 单个热点 key 过期 | 集中访问同一 key | DB 瞬间被打爆 |
| 雪崩 | 大量 key 同时过期 / Redis 宕机 | 全量请求 | DB 全面过载 |
2. 穿透:为什么"查不到"也是攻击面
请求 id=-1 → 缓存 miss → 查 DB(没有)→ 不写缓存 → 下次再来一遍
| 方案 | 说明 | 代价 |
|---|---|---|
| 缓存空值 | 查不到也写 null(短 TTL) |
占用内存,需防大量随机 key |
| 布隆过滤器 | 前置判断"可能存在吗" | 有假阳性、不能删除 |
| 参数校验 | 非法 id 直接拒 | 只能挡低级攻击 |
判据:"无效 key 会不会大量且随机?" 会 → 布隆过滤器;少量 → 缓存空值。
3. 击穿:热点 key 到期那一刻
| 方案 | 说明 |
|---|---|
| 互斥锁重建 | 只让一个请求查 DB,其他等待 |
| 逻辑过期 | 不设 TTL,value 里带过期时间,异步刷新 |
| 热点永不过期 + 后台刷新 | 最简单可靠 |
"互斥锁"的标准写法:
GET cache → miss → SET lock NX PX 3000 成功者查 DB 并回填 → 释放锁
↓ 失败者
短暂 sleep 后重试 GET
注意:这把锁必须是"尽力而为"——锁超时要能自愈(J03 的锁问题同源)。
4. 雪崩:从"同时过期"到"Redis 挂了"
| 原因 | 方案 |
|---|---|
| 大量 key 同一时刻过期 | TTL 加随机抖动 |
| Redis 实例挂掉 | 高可用(哨兵/Cluster,A01)+ 本地缓存兜底 |
| 冷启动 | 预热 |
"TTL 抖动"的具体做法:
long ttl = base + ThreadLocalRandom.current().nextLong(0, jitter);
判据:"这批 key 是不是同一批导入、同一个 TTL?" 是 → 必然同时过期。
5. 三者共用的兜底
| 兜底 | 说明 |
|---|---|
| 限流/熔断 | 保护 DB(S17) |
| 本地缓存 | Redis 挂了还能挡一层 |
| 降级 | 返回默认值/静态页 |
"缓存问题的最终防线不是缓存,是 DB 保护"——限流与熔断永远要在。
6. 五个误区
| 误区 | 真相 |
|---|---|
| "三个问题是一回事" | 触发条件完全不同 |
| "缓存空值就能防穿透" | 大量随机 key 会撑爆内存 |
| "热点 key 设长 TTL 就行" | 到期瞬间仍会击穿 |
| "TTL 都一样比较整齐" | 整齐 = 雪崩 |
| "有了缓存就不怕 Redis 挂" | 需要本地缓存/降级 |
7. 自检
- 三者的触发条件各是什么?
- 穿透的三种方案与代价?
- 击穿的"逻辑过期"怎么做?
- 雪崩的两个根因?
- 为什么说"最终防线是 DB 保护"?
8. 下一篇
S12-J02 · 缓存与数据库一致性
三个问题讲完,下一篇处理更根本的:缓存和数据库怎么写才不会脏。
本篇属于系列《S12 · Redis 与缓存》。旧文来源:A-879/2671/1840。
S12-J01 · 缓存穿透、击穿、雪崩
https://www.clxhxhhr.top/posts/5350/ 评论
0 条
还没有评论,先写一条吧。