Redis 缓存设计实战:穿透、击穿、雪崩与一致性

举报
yd_237615889 发表于 2026/09/14 09:44:05 2026/09/14
【摘要】 博客 · 系统设计 / 后端工程 · 2026-09-14Redis 缓存设计实战:穿透、击穿、雪崩与一致性用好了是加速器,用不好就是"数据库被缓存拖垮"的事故现场。这篇讲清三件事:怎么读写才不出脏数据、三大经典故障怎么防、以及容量与高可用怎么做。工程实践手记 ·2026-09-14 ·约 12 分钟阅读一、先想清楚:缓存买的是什么,代价是什么缓存是性能优化里性价比最高的一招:一次内存命中顶...
博客 · 系统设计 / 后端工程 · 2026-09-14

Redis 缓存设计实战:穿透、击穿、雪崩与一致性

用好了是加速器,用不好就是"数据库被缓存拖垮"的事故现场。这篇讲清三件事:怎么读写才不出脏数据、三大经典故障怎么防、以及容量与高可用怎么做。


工程实践手记 ·2026-09-14 ·约 12 分钟阅读

一、先想清楚:缓存买的是什么,代价是什么

缓存是性能优化里性价比最高的一招:一次内存命中顶掉一次数据库查询,QPS 提升一个数量级。买到的是更低延迟、更高吞吐、更好的可用性(数据库抖动时缓存能顶一阵);付出的是一致性复杂度(缓存与数据库两份数据)、内存成本,以及新的故障模式——三大经典故障都源于"缓存层本身会出问题"。

结论:缓存不是"加上就快"的银弹,它是拿一致性复杂度换性能。接受不了最终一致的业务(如强一致的账户余额),要么不加缓存,要么加锁保证强一致(成本明显更高)。

缓存的设计哲学:假设它随时会失效、随时会挂、随时不一致——然后为每一种假设写好防线。
· · ·

二、读写策略:Cache Aside 是默认答案

策略 读路径 写路径 适用
Cache Aside(旁路缓存) 先查缓存,未命中查库并回填 更新数据库,删除缓存 绝大多数场景的默认选择
Read Through 缓存层封装回源逻辑 同旁路缓存 缓存框架支持时更省事
Write Through 同旁路缓存 同时写缓存与数据库 写少、要求读写一致
Write Behind 同旁路缓存 只写缓存,异步刷库 写吞吐优先、可容忍丢数据

绝大多数团队用 Cache Aside 就够了,但它的两个细节才是事故高发区。写路径为什么是「删缓存」而不是「更新缓存」?因为并发写时"更新缓存"会以错误顺序落地(先发的慢请求后落,覆盖掉新值);而"删除"让下一次读自然回填最新值,还避免了无效写放大。为什么是「先更新库、再删缓存」?反过来在并发读下更容易把旧值刷回缓存。极端并发下仍有极小概率不一致,兜底手段是延迟双删:

1. 更新数据库
2. 删除缓存
3. 延迟几百毫秒,再删一次(覆盖期间被读请求刷回的旧值)

延迟双删是兜底而非银弹,更工程化的做法是订阅数据库变更(binlog)来驱动缓存失效。

· · ·

三、穿透:查不存在的数据

成因:请求的数据在缓存和数据库里都不存在(比如伪造的 ID),缓存永远不命中,每个请求都穿透到数据库。防御三板斧:缓存空值——查不到也写入一个空标记,设较短 TTL(如 60 秒),简单有效,注意防止空值占满内存;布隆过滤器——用位数组预判"这个 key 一定不存在",空间效率极高,代价是极小误判率(可能放过,不会错杀);参数校验——明显非法的 ID 在入口就拦掉。

四、击穿:热点 key 过期的那一秒

成因:某个热点 key 突然过期,同一瞬间大量请求发现未命中,全部涌向数据库去重建缓存。防御两板斧。一是互斥重建:只让一个请求去查库重建,其余等待或短暂重试:

if 缓存未命中:
    if SETNX(lock:key):        # 抢到重建权
        查库并回填缓存
        DEL(lock:key)
    else:
        短暂等待后重试读缓存    # 或直接返回旧值

二是逻辑过期:热点 key 干脆不设物理 TTL,值里带一个逻辑过期时间;读时发现逻辑过期,先返回旧值,再异步触发一个请求去刷新——用「短暂旧数据」换「没有重建洪峰」。

五、雪崩:集体失效或缓存层宕机

成因有两类:大量 key 在同一时刻过期(凌晨批量预热、统一 TTL),或者缓存集群整体不可用。防御四件事:TTL 加随机抖动(基础 TTL 加 0 到 10% 的随机值,把过期时间打散);高可用(主从加哨兵或集群模式,同时明确缓存只是加速层,宕机时要有降级路径);限流与熔断(缓存不可用时,入口限流、对数据库调用熔断——保住数据库,就是保住系统);多级缓存(进程内本地缓存当第一层,挡住最热的那部分流量)。

故障 一句话成因 典型症状 主防御
穿透 查不存在的数据 库 QPS 异常升高、命中率骤降 空值缓存、布隆过滤器
击穿 单个热点 key 过期 某一个 key 引发库洪峰 互斥重建、逻辑过期
雪崩 大批 key 同时失效或集群宕机 全站数据库被打满 TTL 抖动、高可用、限流熔断

六、容量与高可用:那些没人看却会爆的指标

  • 大 key:单个 key 几 MB(比如全量列表),读写都会阻塞网络与线程。治理:拆分成多个小 key、只存必要字段、删除改用异步删除(UNLINK)
  • 热 key:单个 key 每秒几十万次访问,单分片扛不住。治理:本地缓存兜一层、key 加后缀打散到多个分片
  • 淘汰策略:内存不够时的行为要显式配置——缓存场景常用 allkeys-lru 或 allkeys-lfu;别用 noeviction 等着写入失败
  • 必看的四个指标:命中率、被淘汰键数、慢日志、内存碎片率——命中率跌破基线通常预示穿透或雪崩

七、实践清单

  • 读写走 Cache Aside:读先缓存、写先库后删缓存
  • 所有 key 必须有 TTL,且带随机抖动
  • 热点 key 上互斥重建或逻辑过期
  • 查空结果缓存短 TTL,高风险查询上布隆过滤器
  • 缓存不可用时要有降级与限流预案,同时监控命中率与淘汰数
  • 大 key 与热 key 定期巡检,内存淘汰策略显式配置

速查卡

问题 防线
脏数据 先更新库再删缓存,必要时延迟双删
穿透 空值缓存 + 布隆过滤器
击穿 互斥重建 / 逻辑过期
雪崩 TTL 抖动 + 高可用 + 限流熔断
热 key 本地缓存 + key 打散
大 key 拆分 + 异步删除
内存失控 显式配置 LRU/LFU 淘汰策略

写在最后

缓存的设计哲学一句话:假设缓存随时会失效、随时会挂、随时不一致——然后为每一种假设写好防线。做完这层心理建设,Redis 就从「加速器」变成了「系统里最稳的一环」。

下一步 · 给 key 的 TTL 加上随机抖动
顺手看一眼命中率曲线和淘汰指标——两个动作,十分钟,就能排掉雪崩和穿透的大半隐患。系列下一篇候选:CI/CD 流水线、微调 vs RAG 选型、语义化版本自动发版。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。