大促期间缓存同时过期,数据库流量瞬间上涨几十倍,你会怎么区分并处理穿透、击穿和雪崩?
先说结论:穿透是查询本就不存在的数据,缓存和数据库都被反复访问;击穿是单个热点 Key 失效后大量请求同时回源;雪崩是大量 Key 同时失效或缓存整体不可用,引发回源洪峰。三者流量形态不同,治理手段也不同。 穿透可做参数校验、缓存空值或布隆过滤;击穿可用互斥重建、逻辑过期和热点预热;
我先给结论,再说明它在项目里解决什么问题。区分三类缓存故障的流量形态,并设计空值、互斥、随机过期和限流方案。穿透是查询本就不存在的数据,缓存和数据库都被反复访问;击穿是单个热点 Key 失效后大量请求同时回源;雪崩是大量 Key 同时失效或缓存整体不可用,引发回源洪峰。三者流量形态不同,治理手段也不同。 穿透可做参数校验、缓存空值或布隆过滤;击穿可用互斥重建、逻辑过期和热点预热;雪崩要让过期时间离散化,并配合高可用、限流、熔断和降级。
核心机制我会按一次真实执行过程来讲。沿着「请求查询缓存 → 未命中或值过期 → 控制回源并发 → 数据库加载并回填 → 设置随机 TTL 与降级」观察输入、状态与输出,这些阶段都可以从日志、指标或源码里验证。 请求查询缓存 穿透是查询不存在数据,击穿是热点瞬间失效,雪崩是大量 Key 或实例同时失效,三者治理目标不同。 未命中或值过期 缓存查询要区分空值、逻辑过期和真实错误,不能把 Redis 超时当成缓存未命中直接放大回源。
实现细节只抓关键入口,不会整段背源码。redis.call + Lua:热点重建锁原子操作。 INFO keyspace/commandstats:命中与回源证据。 我会先确认请求实际走到了哪条路径,再用运行数据验证,不会只看类名或配置猜测。
放到生产使用时,我会关注参数和验证数据。同时让 1000 请求命中不存在 Key、单热点过期和全量过期三种场景,分别量化数据库放大。 固定输入和基线 先在没有故障注入的环境执行上述配置,固定数据规模、并发度、运行时版本和预热时间。以「回源放大」为主基线,记录值应满足「热点≈1个重建者」;同时保存 命中率按 Key 分布、回源并发,使后续变化能够回到同一时间轴比较。
最后补充常见误区和使用边界。客户端超时后所有请求直接查询数据库,连接池迅速满,缓存恢复后应用仍在排队。加入回源并发闸门、热点本地短缓存和降级数据后,缓存故障不再转化为数据库全量压力。 缓存超时全部直打数据库:命中率按 Key 分布:热点 singleflight/互斥重建。 互斥锁无超时导致热点永久阻塞:回源并发:空值/布隆过滤器。 方案:更适合的场景:主要收益:代价与边界。 缓存空值:不存在查询可枚举且变化不频繁:实现简单:占内存且需短 TTL。 布隆过滤器:Key 规模大、允许极低误判:拦截绝大多数穿透:删除和重建复杂。 逻辑过期+异步重建:热点允许短暂旧值:避免击穿、延迟稳定:一致性和后台任务更复杂。 选型至少带上 Key 数量、Value 大小、命令复杂度、热点分布和过期速率,并用上面的量化基线验证;未知数据应明确为待测假设。