极高 进阶
缓存穿透-击穿-雪崩#
一句话答案#
穿透(查不存在的 key→布隆/空值缓存)、击穿(热 key 过期→互斥锁/永不过期)、雪崩(大量 key 同时过期→随机 TTL/多级缓存)。
核心要点
缓存穿透(Cache Penetration):
现象:查询一个不存在的 key,缓存中没有,每次都直接打到 DB
场景:恶意攻击(大量查询 id=-1 等不存在的数据)
危险:DB 被大量无效查询压垮
解决方案:
1. 缓存空值:查询结果为空时,也在缓存中写入 null(短过期时间如 5min)
缺点:可能缓存大量无效 key
2. 布隆过滤器(推荐):
在请求到达缓存前,先用布隆过滤器判断 key 是否可能存在
不存在 → 直接返回,不查 DB 也不查缓存
缺点:有误判率(误判为存在的占少数),需要维护数据同步plaintext缓存击穿(Cache Breakdown / Hot Key):
现象:某个热点 key 突然过期,大量并发请求同时打到 DB
场景:秒杀商品详情、热门文章的缓存刚好过期
解决方案:
1. 互斥锁(Mutex):
缓存失效时,只让一个线程查 DB 并重建缓存
其他线程等待(或先返回旧数据)
缺点:等待期间性能下降
2. 逻辑过期(Logical Expiration):
key 永不真正过期,但在 value 中存储过期时间戳
发现逻辑过期 → 异步线程重建缓存,同时返回旧数据(不阻塞)
优点:不阻塞请求;缺点:短暂返回旧数据(最终一致)plaintext缓存雪崩(Cache Avalanche):
现象:大量缓存 key 同时过期,或 Redis 实例宕机,流量全部打到 DB
场景:大量 key 设置了相同的过期时间
解决方案:
1. 过期时间随机化:在基础过期时间上加随机偏移(如 TTL + random(0, 300s))
2. Redis 集群高可用:部署主从+哨兵或 Redis Cluster,避免单点故障
3. 限流 + 降级:DB 前加限流,DB 承载不住时返回降级数据
4. 本地缓存(多级缓存):Redis 宕机时,本地缓存(Caffeine)提供兜底plaintext缓存预热(Cache Warming):
现象:系统刚启动 / 大促开始时缓存是空的,瞬时流量全部打到 DB(冷启动雪崩)
解决方案:
1. 启动时主动加载:项目启动后用定时任务/CommandLineRunner 把热点数据写入缓存
2. 大促前手动刷:活动开始前用脚本预先把秒杀商品、热门榜单灌入 Redis
3. 异步预热 + 错峰 TTL:预热时给不同 key 设随机过期时间,避免预热后又集体过期
关键:预热是"雪崩/击穿的事前预防",把第一波流量挡在缓存层plaintext缓存降级(Cache Degradation):
现象:Redis 故障或 DB 扛不住时,需要保证系统不彻底崩溃(可用性优先)
解决方案:
1. 兜底数据:返回默认值/旧数据/静态页(如"商品详情暂不可用")
2. 熔断限流:Sentinel/Hystrix 检测到 DB 压力过大时直接拒绝部分请求,返回降级结果
3. 多级缓存兜底:Redis 挂了走本地缓存(Caffeine),本地也没有再返回默认值
4. 读写分离降级:只保留核心读路径,关闭非核心的写/统计功能
关键:降级是"故障发生时的事中兜底",宁可返回旧/兜底数据也不让请求压垮 DBplaintext面试回答(2分钟版)
缓存穿透、击穿、雪崩是三种不同的缓存异常场景。穿透是指查询一个根本不存在的key,缓存和数据库都没有,每次请求都穿透到DB,常见于恶意攻击。解决方案一是缓存空值并设短TTL如5分钟,二是在请求链路前置布隆过滤器,不存在的key直接拦截,推荐用布隆过滤器。击穿是指某个热点key突然过期的瞬间,大量并发请求同时打到DB。解决方案一是用互斥锁,只让一个线程查DB重建缓存,其他线程等待;二是逻辑过期方案,key永不真正过期,value里存过期时间戳,发现过期后异步刷新,期间返回旧数据保证可用性。雪崩是指大量key同时过期或Redis宕机,流量全量打到DB。核心方案是在基础TTL上加随机偏移比如random(0,300s)打散过期时间,同时部署Redis Cluster保证高可用,再加上本地缓存如Caffeine做兜底。
追问与易错
追问方向:
- “布隆过滤器有什么缺点?如何解决?”→ 存在误判率(假阳性)且不支持删除元素;降低误判率需增大位数组和哈希函数数量;需要删除功能可用 Counting Bloom Filter,每个位置用计数器替代单 bit
- “互斥锁方案的缺点?”→ 大量并发请求等待锁释放,响应时间变长用户体验差;锁本身可能失效(Redis 锁过期但查询未完成)导致多线程同时穿透;实现复杂需考虑锁超时和重试
- “你们项目中用了哪种方案?为什么?”→ 结合场景回答:穿透用布隆过滤器前置拦截(适合数据集固定的场景),击穿用逻辑过期+异步刷新(保证可用性优先),雪崩用随机 TTL + Caffeine 本地缓存兜底
- “缓存预热和降级是什么?”→ 预热是事前预防:系统启动或大促前主动把热点数据加载进缓存,避免冷启动时第一波流量直接打穿 DB,预热时要错峰 TTL 防止又集体过期;降级是事中兜底:Redis 故障或 DB 扛不住时返回默认值/旧数据/静态兜底页,配合熔断限流(Sentinel/Hystrix)拒绝部分请求,宁可返回旧数据也不让系统彻底崩溃。一个防患于未然,一个保命于危难
易错点:
- ❌ 混淆三者的区别——穿透是 key 不存在,击穿是热 key 过期,雪崩是大量 key 同时过期
- ❌ “空值缓存就完美解决穿透了”——海量不存在的 key 会占用大量内存,需设 TTL