缓存穿透/击穿/雪崩
校招考察频度:★ 高频(核心必背·高频生死线) 一句话速记:穿透查“虚无”(库里查无此人,布隆过滤+缓存空值);击穿破“单点”(热点 Key 瞬间到期,互斥锁+逻辑过期);雪崩倒“整片”(海量 Key 同时失效或宕机,TTL 随机打散+多级缓存+熔断降级)。
面试经典高频必考三连:穿透、击穿、雪崩。虽然名字相似,但诱因和防护策略截然不同。面试时必须“先分层下定义,再给出生产解法与工程取舍”。
一、三大问题核心特征对比
| 异常现象 | 现实大白话比喻 | 攻击/故障源头 | 最终打爆的目标 |
|---|---|---|---|
| 缓存穿透 | 恶意分子拿着一个**根本不存在的身份证号(如负数 ID)**去民政局查档案,前台缓存查不到,只能每次都派人去地下档案库(数据库)掘地三尺翻找,最终把库累瘫。 | 恶意扫描攻击或非法请求 | 数据库(持续高压穿透) |
| 缓存击穿 | 某顶级顶流明星突发爆炸性新闻(超级热点 Key),刚好在热度最高峰的那一秒,该 Key 的缓存物理 TTL 刚好到期!百万并发请求同时穿破缓存直击数据库。 | 正常海量并发 + 单点 Key 过期 | 数据库(瞬间单点突刺) |
| 缓存雪崩 | 大批量的 Key 设置了完全相同的过期时间(如全设为 2 小时),到期那一刻全城停电;或者 Redis 整个集群直接物理宕机,所有请求如同雪崩般倾泻至数据库。 | 批量过期或 Redis 宕机 | 数据库(全盘崩溃瘫痪) |
二、缓存穿透:布隆过滤器深度剖析
面对“查无此人”的查询,解法有二:
- 缓存空对象(Cache Null / Empty):
- 数据库查不到时,向 Redis 写入空值(如
""或{}),设置极短的 TTL(如 1~3 分钟)。 - 缺点:如果攻击者每次换不同的无序随机 ID,依然会产生大量空 Key 占满 Redis 内存。
- 数据库查不到时,向 Redis 写入空值(如
- 布隆过滤器(Bloom Filter,工业界标配):
- 底层原理:由一个超长二进制位数组(Bit Array)和 个独立的哈希函数组成。写入数据时,分别用 个哈希函数计算出 个位置并置为 1;查询时,必须这 个位置全部为 1 才认为可能存在。
- 铁律法则:
- 只要有任意 1 位是 0,该数据 100% 绝对不存在(可以直接拦截,不查 DB);
- 若全为 1,数据大概率存在,但存在一定的“假阳性/误判率(False Positive)”(因为哈希碰撞,这几位可能恰好被其他数据置为 1 了)。
- 删除问题:标准布隆过滤器不能删除数据(清 0 可能会连带影响其他数据);若需支持删除,需采用计数布隆过滤器(Counting Bloom Filter)。
缓存击穿
现象:热点 key(如秒杀商品)在过期瞬间,大量并发请求同时 miss,一起打到数据库。
解法:
- 互斥锁:第一个请求拿锁重建缓存,其他请求等待或返回旧值。锁粒度要细,避免串行化所有读
- 逻辑过期:不设物理 TTL,value 里带过期时间戳;读到逻辑过期时异步重建,请求先返回旧数据。实现复杂但无锁等待
缓存雪崩
现象:大量 key 同一时刻过期(如统一 TTL),或 Redis 实例宕机,流量瞬间全部打到数据库。
解法:
- TTL 打散:过期时间加随机值(基础值加 0 到 300 秒随机),避免整批过期
- 多级缓存:本地缓存(如 Caffeine)挡第一层,Redis 第二层,数据库兜底
- 高可用:哨兵/集群防单点宕机,宕机期间限流降级
- 限流降级:数据库侧限流,返回降级数据(如旧缓存、默认值)
对比与优先级
- 穿透和击穿、雪崩的防护可以叠加:布隆过滤器挡穿透,TTL 打散挡雪崩,互斥锁挡击穿
- 面试话术顺序:先说现象和危害,再说方案,再说取舍(空值缓存的内存代价、布隆过滤器的误判率、锁的粒度)
面试追问
- 穿透和击穿的区别? 穿透是数据不存在每次打库;击穿是热点 key 过期瞬间打库。一个是”有没有”的问题,一个是”过期时机”的问题
- 布隆过滤器有误判吗? 有,只能保证”不存在的一定不在”,存在可能误判。误判率由位数组大小和哈希函数数控制
- 互斥锁怎么实现? SETNX 拿锁重建缓存,其他请求拿不到就等或返回旧值。注意锁要设过期时间防死锁
- 逻辑过期和互斥锁怎么选? 要求强一致用互斥锁(有短暂等待),允许短暂旧数据用逻辑过期(无等待)
- 雪崩的 TTL 打散怎么做? TTL = 基础时间 + 随机偏移,让过期时刻均匀分布