跳转到内容
seren-spark

07 缓存穿透、击穿、雪崩与热点问题

缓存出问题时,先判断问题是哪一种

Section titled “缓存出问题时,先判断问题是哪一种”

学习目标 · 能区分穿透、击穿、雪崩、热 Key 和大 Key,给出带代价的解决方案。

问题具体发生了什么首选措施代价 / 边界
穿透不存在的商品被反复请求,缓存和 DB 都没有参数检查、短 TTL 空值缓存,必要时布隆过滤器新建数据需失效占位;布隆“可能存在”仍要查
击穿一个热门 Key 到期,大量请求同时回源合并同 Key 请求、互斥重建,或逻辑过期等待增加延迟;旧值方案牺牲新鲜度
雪崩大量 Key 同时过期,或 Redis 整体不可用TTL 抖动、容量与高可用、回源限流、降级TTL 抖动不能修复 Redis 整体故障
热 Key单个 Key 访问量极高本地缓存、合并请求、按业务拆热点多副本失效复杂;Cluster 不自动拆一个 Key
大 Key一个 Key 的字节数或元素数过大缩减字段、分页、分桶、分批迁移 / 删除需要改模型;只缩短 Key 名帮助有限

7.2 布隆过滤器:把“不可能”挡在前面

Section titled “7.2 布隆过滤器:把“不可能”挡在前面”

用一组哈希把已有 ID 标记到位图。查询若发现必要位置没有标记,可以判定“不存在”;所有位置都标记,只能判定“可能存在”。这是因为不同 ID 可能映射到同一位置。正确构建、同步且不做不安全删除的前提下没有假阴性,但有假阳性。新商品未同步进过滤器,会造成业务上的错误拦截。

它适合大量、稀疏的 ID 集合查询,不是每个 CRUD 项目都要上。Redis 8 包含相关概率结构能力,老版本/部署形态可能需要模块;本教程先用空值缓存解决常见情况,不要求手写布隆实现。

概念流程 · cache miss 之后
1. 尝试为“这个商品”获取短期互斥锁,而不是锁住全部商品。
2. 获取成功:再次查缓存;别人可能刚刚重建过。
3. 仍未命中:查询数据库,设置缓存,按持有者令牌释放锁。
4. 获取失败:带随机抖动短暂等待,再读缓存;总等待时间有上限。
5. 超过接口预算:返回可接受的旧值或受控错误,不能无限自旋。

锁实现见第 8 章。锁服务与缓存一起失效时,该流程也会失效,因此还需要应用层的回源并发上限。单机请求合并只能约束一个实例,十个实例仍可能各回源一次;是否需要跨实例协调,由数据库能否承受与业务代价决定。

7.4 逻辑过期:允许旧一点,保持能访问

Section titled “7.4 逻辑过期:允许旧一点,保持能访问”

缓存值中存 data + freshUntil。读取发现逻辑时间过期时,先返回旧 data,仅由一个任务异步重建。后端后台线程池必须有界,避免每个请求 new 线程;物理 TTL 可更长,但仍需容量和最终清理策略。

请求读到 data
→
freshUntil 已过
→
立即返回旧 data
取得重建资格
→
有界后台任务
→
回源 → 覆盖新 data
图 04 · 逻辑过期用新鲜度换可用性。首次就没有缓存、后台一直失败、旧值超出业务容忍期时,都要有明确处理。

适合活动页介绍、热门店铺资料等可接受短期旧值的场景;不适合拿旧库存批准下单。设定最大可接受陈旧时长,超过后限流回源或返回降级结果,不能永久返回错误数据。

假设正常数据库能承受 800 QPS,缓存故障时入口却有 10,000 QPS。“所有请求 try/catch 后查数据库”仍会压垮它。需要结合每实例并发限制与实例总数、熔断、接口限流、请求合并、可接受的本地旧值,以及明确的繁忙响应。恢复时逐步放量或预热热点,避免空缓存再次集中回源。

降级不是都返回空列表:商品不存在、服务不可用、权限校验失败必须区分。鉴权和写入正确性有关的状态,故障时不能无条件放行。

  • “布隆过滤器说存在,数据库就一定存在”:它允许假阳性。
  • “加了 6 个 Redis 节点,单 Key 热点就分散了”:分片按 Key 路由,同一 Key 仍归属一个槽所在的主节点。
  • “逻辑过期永远不失效”:物理 Key 仍可能因内存淘汰、重启或故障消失。

分别为“有人随机枚举商品 ID”“同一个爆款刚到期”“Redis 所有请求超时”写下第一条缓解措施及观察指标。再解释它不能解决的另两种问题。

自测与答案 · TTL 随机抖动能解决哪些问题?

主要分散大量 Key 的集中到期,降低一类雪崩概率。它不解决不存在 ID 的穿透、不保证单热点到期不击穿,也不能恢复宕机 Redis。练习:枚举 ID 看空结果与回源量,先校验及空值缓存;爆款到期看单 Key 回源并发,合并或互斥重建;整体超时先守住数据库,限流熔断并排查 Redis/网络。

继续阅读:布隆过滤器 · 重建互斥的锁边界