跳到主要内容

缓存穿透、击穿、雪崩与热点治理

缓存异常通常表现为大量请求同时回到数据库,但原因并不相同。穿透来自不存在的数据,击穿来自单个热点失效,雪崩来自大量 key 或缓存节点同时不可用,热点 key 则可能直接压满 Redis 的单个执行路径或网络带宽。

1. 缓存穿透持续查询不存在的数据

请求的 key 在缓存和数据库中都不存在时,每次读取都会回源。恶意随机 key、无效参数和已经删除但仍被频繁访问的对象都可能产生穿透。

处理方式包括:

  • 在入口校验 ID 格式、租户和可访问范围。
  • 对“确实不存在”的结果设置较短负缓存。
  • 使用 Bloom filter 快速排除肯定不存在的 key。
  • 对异常来源限流,并监控不同结果类型的回源比例。

Bloom filter 可能把不存在的数据误判为“可能存在”,所以仍要查数据库;它不会把真实存在的数据判断为不存在,前提是过滤器正确维护。负缓存也要考虑数据随后创建时如何失效。

2. 缓存击穿发生在单个热点失效时

一个高频 key 到期或被删除后,大量并发请求同时未命中,并一起查询数据库。数据库面对的是同一个昂贵查询,而不是均匀分散的流量。

常见方法有:

  • 合并加载:一个请求回源,其余请求等待同一个结果。
  • 互斥重建:只允许持有短租约的请求重建缓存。
  • 逻辑过期:先返回可接受的旧值,再由后台刷新。
  • 提前刷新:在物理过期前按访问热度更新。

等待队列必须有上限和超时。若加载方失败,其他请求要能结束或由下一位接管,不能永久等待一把重建锁。

3. 缓存雪崩让大量 key 同时失效

大量 key 使用相同 TTL 并在同一时刻到期,或者 Redis 分片、网络、客户端连接池整体故障时,回源量会突然上升。数据库平时按缓存命中后的流量配置,很可能无法承受全部请求。

可以组合使用:

  • 在基础 TTL 上加入随机抖动,分散到期时间。
  • 分批预热,避免部署后同时写入相同有效期。
  • 对回源设置并发上限、排队上限和快速失败。
  • 保留降级数据或暂时关闭非关键查询。
  • 为缓存和数据库分别做容量压测与故障演练。

高可用 Redis 可以缩短节点故障,但主从切换和客户端重连期间仍会产生错误与延迟,不能替代回源保护。

4. 热点 key 可能在未失效时成为瓶颈

单个 key 的请求集中到同一个 Redis 分片。即使命中率为 100%,该分片的 CPU、网卡或连接也可能先达到上限。

先从命令统计、采样、代理日志和业务指标识别热点,再根据数据语义处理:

  • 只读且允许短暂旧值时,使用进程内缓存或客户端缓存。
  • 可拆分的数据按后缀复制多份,让读取随机分散。
  • 计数等可合并写入使用分片计数,再按需聚合。
  • 对单一来源或业务动作限流。

复制热点 key 会增加失效和一致性成本。写热点若要求全局严格顺序,单纯复制不能解决冲突。

5. 大 key 会放大单次操作成本

很大的 String、Hash、List、Set 或 Sorted Set 会占用较多内存和网络带宽。删除、序列化、复制、迁移或遍历大 key 时,还可能产生延迟尖峰。

应限制单值大小和集合元素数量,使用渐进式扫描,按业务维度拆分,并避免在线路径执行返回全部元素的命令。删除大 key 时可以根据 Redis 版本和场景使用异步释放能力,但内存最终仍需回收。

6. 防护顺序从入口到数据库

一条完整的防护链可以是:

  1. 参数和权限校验拦截无效 key。
  2. 限流控制单来源与单业务键流量。
  3. 本地或 Redis 缓存承接正常命中。
  4. 合并同 key 的并发回源。
  5. 数据库使用连接池和查询超时限制压力。
  6. 失败时返回可接受的旧值、缺省结果或明确错误。

每层都需要上限。无限等待、无限队列和无限重试只是把故障向后推迟。

7. 故障演练验证回源能力

应分别演练单个热点过期、大批 key 过期、Redis 分片不可用、连接池耗尽和数据库变慢。观察命中率、回源 QPS、合并加载等待、Redis 各分片 CPU/网络、数据库连接等待和接口尾延迟。

缓存恢复后还要控制预热速度。所有请求同时重建缓存,可能在恢复阶段再次打满数据库。

8. 常见问题

8.1 热点 key 永不过期是否能避免击穿

物理不过期可以避免 TTL 到点造成的击穿,但数据仍需要更新,也可能因为淘汰、节点故障或人工删除而消失。还要设计逻辑过期、后台刷新和失败时返回旧值的边界。

8.2 Bloom filter 是否可以代替数据库查询

不能。Bloom filter 只能确定“肯定不存在”或“可能存在”。可能存在仍需查询缓存或数据库,而且过滤器要处理新增、重建和误判率增长。

9. 面试题

9.1 缓存穿透、击穿和雪崩分别如何处理

出现公司:字节跳动

考察重点

  • 三类问题的流量来源和影响范围。
  • 负缓存、Bloom filter、合并加载和 TTL 抖动。
  • 回源限流、降级与故障恢复。

相关内容:第 1 节“缓存穿透持续查询不存在的数据”至第 7 节“故障演练验证回源能力”。

参考回答

穿透是持续查询缓存和数据库都不存在的数据,可以用参数校验、短期负缓存、Bloom filter 和来源限流减少无效回源。击穿是单个热点 key 失效后并发回源,可以合并同 key 加载、互斥重建、逻辑过期或提前刷新。雪崩是大量 key 同时失效或缓存整体故障,应打散 TTL,并对回源设置并发上限、降级和容量保护。

还要单独关注未过期的热点 key 和大 key,它们可能打满某个 Redis 分片的 CPU、网络或内存操作。治理效果需要通过分片指标、回源量和故障演练验证,不能只看整体命中率。