缓存穿透、击穿、雪崩的成因与解决方案
【摘要】 一、为什么缓存会“出问题”缓存是性能利器,但把流量挡在数据库前面后,一旦缓存层异常,问题会瞬间放大到数据库。面试和实战里最常被问的三个经典问题——缓存穿透、缓存击穿、缓存雪崩——名字相近,成因和解法完全不同,必须分清。 二、缓存穿透(Cache Penetration)成因:查询一个根本不存在的数据(如 id = -1 或随机不存在的 key)。缓存里没有,数据库里也没有,于是每次请求都穿...
一、为什么缓存会“出问题”
缓存是性能利器,但把流量挡在数据库前面后,一旦缓存层异常,问题会瞬间放大到数据库。面试和实战里最常被问的三个经典问题——缓存穿透、缓存击穿、缓存雪崩——名字相近,成因和解法完全不同,必须分清。
二、缓存穿透(Cache Penetration)
成因:查询一个根本不存在的数据(如 id = -1 或随机不存在的 key)。缓存里没有,数据库里也没有,于是每次请求都穿过缓存打到数据库。恶意攻击者可以构造大量不存在的 key 打垮数据库。
解法:
- 缓存空值:查不到也把
null/空标记写入缓存(设较短过期时间,如 60 秒),下次直接命中空结果。 - 布隆过滤器(Bloom Filter):把所有存在的 key 放进布隆过滤器,请求先过一遍,明确不存在的直接拦截,不查库。注意布隆过滤器有极小误判率(只会误判“存在”,不会漏判),且不支持删除(要用计数版或定期重建)。
- 参数校验:入口做合法性校验(id 必须为正整数、格式校验),挡掉明显非法的请求。
三、缓存击穿(Cache Breakdown)
成因:某个热点 key 过期的一瞬间,大量并发请求同时发现缓存失效,全部涌向数据库去重建这个 key。注意:数据是存在的,只是缓存刚好过期,和“穿透”不同。
解法:
- 互斥锁(Mutex):只允许一个请求去数据库加载并回填缓存,其余请求等待/重试。用 Redis
SET NX做锁即可。 - 逻辑过期:缓存里不设物理过期,而存一个逻辑过期时间字段;发现逻辑过期后,由一个线程异步去重建,其它请求先返回旧值(保证可用性)。适合允许短暂旧数据的场景。
- 热点 key 永不过期 + 后台刷新:对极热数据不设 TTL,靠后台任务定期更新。
四、缓存雪崩(Cache Avalanche)
成因:大量 key 在同一时间集中过期,或缓存服务整体宕机,导致所有请求瞬间打到数据库。和“击穿”的区别是:击穿是单个热点 key,雪崩是大量 key 同时失效或整个缓存层挂了。
解法:
- 过期时间加随机抖动:给 TTL 加一个随机值(如
30min + random(0~5min)),避免同一时刻集体过期。 - 缓存高可用:Redis 用主从 + 哨兵 / 集群,避免单点宕机。
- 限流降级:数据库前加限流,缓存不可用时走降级逻辑(返回兜底数据/默认值),保护数据库。
- 多级缓存:本地缓存(Caffeine)+ 分布式缓存(Redis),一层挂了还有另一层缓冲。
- 预热:启动或活动前提前把热点数据加载进缓存,避免冷启动瞬间打库。
五、三者对比
| 问题 | 数据是否存在 | 触发条件 | 核心解法 |
|---|---|---|---|
| 穿透 | 不存在 | 查不存在的 key | 缓存空值 / 布隆过滤器 |
| 击穿 | 存在(热点) | 单个热点 key 过期 | 互斥锁 / 逻辑过期 |
| 雪崩 | 存在(大量) | 大量 key 同时过期或缓存宕机 | 随机 TTL / 高可用 / 限流降级 |
六、代码示例:互斥锁防击穿
def get_user(user_id):
key = f"user:{user_id}"
val = redis.get(key)
if val is not None:
return val
# 缓存失效,尝试抢锁重建
lock_key = f"lock:{key}"
if redis.set(lock_key, "1", nx=True, ex=10):
try:
# 双重检查,避免拿到锁前别人已回填
val = redis.get(key)
if val is not None:
return val
user = db.query_user(user_id)
redis.set(key, user, ex=300)
return user
finally:
redis.delete(lock_key)
else:
# 没抢到锁,短暂等待后重试读缓存
time.sleep(0.05)
return get_user(user_id)
七、常见坑
- 把三个概念混为一谈:用错解法,比如用布隆过滤器解决雪崩(无效)。
- 缓存空值不设过期:空值永久占用内存且掩盖数据后续新增。
- 互斥锁不设过期:持锁进程崩溃导致死锁,必须带 TTL。
- 逻辑过期返回旧值不设上限:重建失败时一直返回很旧的数据,要有兜底。
- TTL 全一致:批量写入缓存时用了同一个 TTL,等于埋下雪崩隐患。
八、总结
- 穿透:防“查不存在的”,用缓存空值 + 布隆过滤器。
- 击穿:防“热点 key 过期瞬间并发”,用互斥锁 / 逻辑过期。
- 雪崩:防“大面积失效或缓存宕机”,用随机 TTL + 高可用 + 限流降级 + 多级缓存。
一句话记忆:穿透是“没有也查”,击穿是“热点过期”,雪崩是“集体失效”。分清场景再选方案,才能真正守住数据库。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)