跳转到内容

缓存穿透、击穿与雪崩

用途:解释“缓存突然失效导致数据库被打挂”这类故障,并给出对策

问题 定义 典型征兆
穿透 查询根本不存在的数据,缓存无值,每次打到 DB DB QPS 高但命中率低;key 分布异常(大量不存在的 ID)
击穿 热点 key 过期瞬间大量请求同时回源 单点毛刺,DB 瞬时并发尖峰
雪崩 大批 key 同时过期或缓存实例整体不可用 周期性尖峰,或缓存故障后 DB 崩溃
  1. 空值缓存:查不到也写 NULL 占位(短 TTL,如 1-5 分钟)。实现简单,代价是内存与“新数据插入后的窗口不一致”。
  2. 布隆过滤器:前置判断 key 是否可能存在(RedisBloom 或本地 Guava/ScalableBloomFilter)。注意:不支持删除(需用计数型/分区方案),存在假阳性。
  3. 输入校验:对明显非法的参数(格式、范围、负数 ID)在入口拦截。
  4. 接口限流:对未知 key 的高频访问做单来源限流,防刷。
  1. 互斥回源:只允许一个线程回源,其余等待或返回旧值。实现:SET key_lock NX EX,或本地 singleflight。
  2. 逻辑过期:value 内嵌过期时间,读到“逻辑过期”时异步刷新、同步返回旧值。彻底避免回源风暴,代价是短暂脏读。
  3. 热点常驻:真热点不设 TTL,由后台任务定期刷新(配合变更事件失效)。
  4. 永不过期 + 主动失效:写入路径负责删除/更新缓存,读路径不过期。
  1. TTL 加随机抖动:ttl = base + rand(0, 0.2*base),避免同刻集中失效。
  2. 多级缓存:本地缓存(Caffeine)+ 分布式缓存,本地层吸收大部分瞬时并发。
  3. 缓存高可用:哨兵/Cluster、跨可用区部署、客户端熔断与降级(缓存不可用时限流回源而不是全量回源)。
  4. 预热:发布或重启后按访问频率预加载热点。
  5. DB 侧兜底:连接池上限 + 慢查询熔断 + 读接口降级(返回近似数据或缓存旧值)。

无论哪种问题,最后一道防线都是限制回源并发。做法:信号量/令牌桶限制“正在查 DB 的请求数”,超出的请求直接返回降级结果。这比让 DB 排队更可控——数据库的排队能力是它最差的资源。

误区 修正
用 SETNX 锁但忘记释放或过期时间 锁必须带 TTL,否则死锁;释放要判断持有者(值用唯一 token)
空值缓存 TTL 与正常值一样长 空值应更短,避免新数据长期不可见
布隆过滤器当权威判据 假阳性会放行部分穿透请求,仍需 DB 兜底
所有 key 设同一个整点过期 制造雪崩;TTL 必须打散
只加缓存不做容量与故障演练 缓存故障时的 DB 承载能力必须实测
  • Martin Kleppmann《数据密集型应用系统设计》第 1 章(缓存一致性与失效)
  • Caffeine、RedisBloom 官方文档