缓存穿透、击穿、雪崩与热点治理
缓存异常通常表现为大量请求同时回到数据库,但原因并不相同。穿透来自不存在的数据,击穿来自单个热点失效,雪崩来自大量 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. 防护顺序从入口到数据库
一条完整的防护链可以是:
- 参数和权限校验拦截无效 key。
- 限流控制单来源与单业务键流量。
- 本地或 Redis 缓存承接正常命中。
- 合并同 key 的并发回源。
- 数据库使用连接池和查询超时限制压力。
- 失败时返回可接受的旧值、缺省结果或明确错误。
每层都需要上限。无限等待、无限队列和无限重试只是把故障向后推迟。
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、网络或内存操作。治理效果需要通过分片指标、回源量和故障演练验证,不能只看整体命中率。