REJAVA / LEARNING NOTES
第19课速查:缓存穿透、击穿与雪崩
随时回查术语、规则和示例;完整解释见对应课程。
根据课程材料整理
本次更新说明
- 整理为公开学习笔记,保留要点、对照表与练习入口。
cache-aside(旁路缓存)两条路
- 读:查缓存 → 未命中查MySQL(关系型数据库) → 写回缓存(带过期时间)→ 返回。
- 写: 先改数据库,再删缓存 ;反过来会把旧值放回缓存(微软架构中心)。
- 三种问题都是:某个时刻大量请求同时走了“未命中查库”这条路。
三种情况一张表
| 叫法 | 越过缓存的是 | 首选办法 | 代价 |
|---|---|---|---|
| 穿透 | 查数据库里也不存在的数据 | 参数校验;查不到缓存短过期空值;布隆过滤器;限流 | 换新编号挡不住、新建要删占位;过滤器误判、不能删、要预热持久化 |
| 击穿 | 一个热门键过期的瞬间 | 热门数据预热并不过期;拿锁重建;逻辑过期先返回旧值 | 请求要等;短暂旧数据 |
| 雪崩·同时过期 | 大量键同一时刻过期 | TTL = 初始TTL + 随机值;预热分批 | 过期时间不整齐 |
| 雪崩·Redis(内存数据存储)不可用 | 全部请求 | 本地缓存兜底;数据库限流降级;Sentinel/Cluster高可用;关缓存压测 | 本地数据更旧;部分请求被拒 |
“穿透/击穿/雪崩”是国内叫法;官方对应说法:negative cache(负缓存)、thundering herd(惊群)与 request coalescing(请求合并)、TTL(剩余存活时间)加抖动。
布隆过滤器:Redis(内存数据存储) 8 起内置
BF.RESERVE case:ids 0.001 2000000:误判率0.1%、容量200万;超过容量自动扩容,误判率和速度变差。BF.MADD启动时装入全部编号,新建时BF.ADD;查询前BF.EXISTS。- 返回0=一定没加入( 键不存在也返回0 ,重启丢了会拒掉所有真实请求);返回1=可能加入过。
- 没有删除命令;要删除用 Cuckoo filter(布谷鸟过滤器,
CF.DEL)。
拿锁重建的四个细节
- 拿锁与过期一条命令:
SET lock:case:20 随机值 NX PX 10000(Java:setIfAbsent(k, token, Duration))。 - 值是每次唯一的随机值。
- 释放时比较值相等才删:Redis(内存数据存储) 8.4起
DELEX 键 IFEQ 随机值,更早用Lua脚本;放在 finally(最终清理代码块) 里。 - 拿到锁后再查一次缓存(双重检查);没拿到锁时有上限地等待重试。
官方把简单SET NX锁标为不推荐(建议Redlock),单实例锁只适合“偶尔竞争可接受”——缓存重建正是这种场景。
Spring 相关
@Cacheable(sync = true):让缓存提供者锁住条目只算一次;官方说只是提示,不能与 unless 同用,是否跨机器取决于提供者。- Spring Data Redis(Spring 的内存数据存储访问模块) 的 RedisCacheConfiguration 默认缓存 null(空值)、默认不过期;用
entryTtl、disableCachingNullValues调整。
一分钟表达
三者都是大量请求越过缓存打到数据库。穿透是查不存在的数据,靠参数校验、缓存短过期空值和布隆过滤器挡,布隆过滤器说不存在一定不存在、说存在可能误判、不能删除。击穿是一个热门键过期瞬间大量并发回源,靠热门数据不过期或拿锁重建,锁要一条命令带过期、用随机值、比较后再删,拿到锁后再查一次缓存。雪崩是大量键同时过期或Redis(内存数据存储)整个不可用,前者给过期时间加随机抖动,后者靠本地缓存兜底、限流降级和高可用部署,并在关掉缓存时压测。
回到应用题判断类型、审查代码。表中命令和代码按官方文档核对,没有实际运行。