面向 P6-P7+ 工程师 框架训练型 · 长文 约 9,000 字 信息截止 2026-08

亿级流量多级缓存:
本地缓存 + Redis + CDN 防击穿实战

这篇文章不是 Redis GET/SET 入门教程,而是把一次「大促零点热 key 集体过期 + Redis Cluster 主从切换 + CDN 回源风暴」 叠加成数据库连接池打满的复合事故,还原成可复用的多级缓存决策框架: 击穿/雪崩/穿透如何在值班话术里分诊、Caffeine 与 Redis 7.x 的分层契约如何写进代码评审、 CDN 边缘缓存与动态数据的边界在哪里,以及上线前必须写进 RACI 的防护检查表。

主线风格:框架训练 50% + 问题现场 35% + 架构 15% 版本假设:Redis 7.x Cluster/Sentinel;本地缓存 Caffeine 3.x 量级 证据等级:官方文档优先;性能数字为合成示例;标注推断与生产经验
问题现场 · 复合场景

1复合现场:热点击穿与 Redis 雪崩重叠

复合场景 · 综合电商大促详情页 + Redis Cluster 主从切换 + CDN 回源风暴的典型对话,非指代单一具体事件 COMPOSITE SCENARIO

某综合电商在六一大促零点前 30 分钟开启全链路压测。商品详情页 QPS 从日常 8.2 万抬升至 19.6 万(合成示例), 读路径为 CDN → 网关 → 商品服务(Caffeine 本地缓存)→ Redis Cluster → MySQL 只读组。 运营在 T-20 min 按活动页脚本批量预热 Redis,约 1.2 万个 SKU 详情 key 的 TTL 被统一设为 300 s,且未加 jitter。 T+0 流量洪峰到达时,Caffeine 命中率仍显示 94%,架构组误判「缓存层健康」。

T+3 min,监控显示 Redis 某 hot shard ops 从 12 万/s 升至 48 万/s,同时 MySQL 只读实例 active connections 从 320 升至 980, 触发动态连接池上限与慢查询队列。T+6 min,CDN 回源带宽从 18 Gbps 跳至 63 Gbps,源站 nginx 499 比例升至 2.7%(合成示例)。 同一 traceId 抽样显示:miss 本地缓存的请求在 40 ms 内集中打向同一 Redis key item:detail:8810221,该 key 恰在 T+2 min 过期——典型热点击穿窗口与集体过期窗口重叠。

T+9 min,Redis Sentinel 对 overload 分片触发 failover,客户端出现 200 ms 级别的 MOVED/ASK 风暴; 业务方报警:购物车合并接口 P99 从 120 ms 升至 890 ms,并非写路径故障,而是读依赖链路上的缓存失效放大。 现场困惑在于:本地命中率不低、Redis 并非全库 miss,为何 DB 连接池仍被打满?为何 CDN 回源与源站 CPU 同时尖刺? 这三个问题叠加,正是本文要拆解的多级缓存复合失效模式——单层指标健康不等于读路径 SLA 健康。

事后复盘发现:该商品在 Caffeine 中仅 2000 条热点 SKU 有 entry,其余长尾 SKU 完全依赖 Redis; 活动页 80% 流量集中在 300 个爆款,其中 37 个 key 在同一秒过期(合成示例算术)。 架构组曾认为「94% 本地命中足够」,却忽略了miss 流量乘数:1% miss × 19.6 万 QPS 仍等于 1960 次/s 穿透到 Redis, 再乘以未加互斥的并发 rebuild,即可在毫秒内形成「逻辑上的 DB 风暴」。这是典型的容量直觉替代 miss 乘数计算失误。

上述场景折射出一个普遍误区:把缓存可用性等同于「Redis 集群 green + 命中率 > 90%」。 在亿级读流量里,miss 乘数、热点 key 拓扑与 TTL 时间相关性才是 SLA 的交汇点: 本地缓存降低平均延迟,却会把长尾 miss 压缩成更短的尖刺窗口;Redis 集体过期会在同一时刻制造 fan-in; CDN 若缓存键未区分活动态,会在源站抖动时放大回源。不理解这三层绑定,容易在「指标尚可」时做出加剧击穿的操作—— 例如临时调短 TTL「加快一致性」或在大促前全量 flush 本地缓存「求稳」。

本书《亿级流量网站架构核心技术》强调:缓存的本质是用可控的不一致换取吞吐与延迟。 本文默认 Redis 7.x Cluster/SentinelCaffeine 3.x 作为 JVM 侧本地缓存实现, CDN 以主流厂商边缘节点为抽象(不绑定单一 API)。性能数字若无说明均为合成示例,机制边界以官方文档为准。 阅读前提:你已理解 cache-aside、TTL、单飞(singleflight)等基础概念;本文聚焦击穿/雪崩/穿透分诊、分层不变量与上线检查表, 目标读者为处理过大促缓存事故的 P6-P7+ 工程师。篇幅分配:框架训练约 50%,问题现场约 35%,体系架构约 15%。

已核验事实

Redis 官方文档说明:过期 key 的删除策略为惰性删除与定期抽样相结合,大量 key 在同一时刻过期会导致 CPU 与延迟抖动, 并放大同时到达的读请求对底层存储的压力。Caffeine 文档说明:基于 W-TinyLFU 的 admission 与 Window-TinyLFU 结构用于在有限内存下提高命中率, 但不能替代分布式一致性与热点互斥。来源: Redis · KeyspaceCaffeine Wiki

1.1 告警时间线:击穿与雪崩信号交织

复合缓存失效的识别特征是三类信号在同一时间窗重叠,而非单一 Redis 超时。 下表按压测时间线整理(数字为合成示例,用于值班分诊顺序训练):

时刻本地缓存Redis源站 / DB
T+0~2 min命中率 94%,P99 8 msops 平稳,慢查询 0连接池 35% 使用
T+2~4 min爆款 miss 尖刺单 key QPS 超 10 万/s(合成)只读组 threads_running 上升
T+4~7 min全节点 rebuild 并发多 key 同时过期,CPU 80%+连接池打满,慢 SQL 队列
T+7 min+部分节点 LRU 抖动failover + 客户端重定向CDN 回源带宽尖刺

上表刻意把「本地命中率」与「Redis 单 key ops」并列:许多 runbook 只写「Redis 挂了怎么办」,忽略 Caffeine miss 乘数与热点 rebuild 并发。 合成示例中 T+2 min 的 item:detail:8810221 过期与 37 个活动 key 集体过期叠加,使 shard 级 fan-in 在 3 s 内完成从「可承受」到「击穿」的跃迁—— 这是值班口诀 「慢的是单 key 还是全库?过期是否对齐?miss 乘数算过没有?」 的物理层证据。

图 1 · 复合失效因果链:从 TTL 对齐到 DB 连接池
因果链
TTL 对齐 无 jitter 集体过期 Redis CPU 尖刺 热 key 击穿 无互斥 rebuild miss 乘数放大 DB 连接池耗尽 CDN 回源风暴 反馈环:源站变慢 → TTL 内 miss 增加 → 更难恢复
读图方式:从左到右读触发链;虚线表示延迟恶化对 miss 率的反向放大。干预点应优先打破「TTL 对齐」或「rebuild 无互斥」任一环节。

1.2 复合场景标注与证据等级

本文所有「T+xx min」时间线均为合成示例,用于训练值班顺序,不代表任何单一生产事故的精确复刻。 机制描述优先引用 Redis 7.x 与 Caffeine 3.x 官方文档;带「推断」字样的结论来自常见大促架构复盘模式,需结合本业务压测验证。 复合场景定义:至少两种缓存失效机制在同一 15 min 窗口内叠加,且单一扩容或 flush 动作可能恶化另一种机制——第 1 章即 TTL 对齐(雪崩倾向)加热 key 无互斥(击穿)加 CDN 回源(边缘放大)。

资深架构师在复盘时会问:若只修复 Redis failover,能否自动消除 CDN 尖刺?若只 purge CDN,会否暴露仍对齐的 TTL 集体过期? 这两个问题的答案通常为「否」,因此 postmortem action items 必须分链列出,而非「优化缓存」一句空话。 合成示例中,DB 连接池打满发生在 T+6 min,而 Redis failover 在 T+9 min——说明击穿窗口先于基础设施切换,根因排序应把「互斥缺失」排在「集群切换」之前。

体系架构 · 分层不变量

2三级缓存架构与分层不变量

多级缓存不是「多贴几层 GET/SET」,而是一组分层不变量(invariants):每一层必须声明自己解决的是延迟、吞吐还是成本, 以及允许的不一致窗口。典型读路径为 CDN 边缘 → 应用进程内 Caffeine → Redis Cluster → 权威数据源(DB 或聚合服务)。 任一层不得 silently 改变 key 语义:CDN 缓存的是 HTTP 表征,Caffeine 缓存的是反序列化后的领域对象,Redis 缓存的是共享字节序列—— 三层若对「活动价 / 库存 / 用户态」的键空间划分不一致,会出现「边缘命中但业务错价」的更难排查问题。

不变量 A(CDN):只缓存可公开、可版本化的 GET 响应;带 Cookie 的个性化页面默认 bypass 或 private cache。 不变量 B(Caffeine):进程内最终一致,生命周期与发布/扩容事件绑定,必须可一键失效或版本戳切换。 不变量 C(Redis):集群内共享一致视图,承担跨实例协调与热点吸收;TTL 必须带 jitter,热点 key 必须可识别。 不变量 D(源站):任何缓存 miss 路径必须有超时 + 降级 + 限流,否则缓存只是延迟爆炸而非消除爆炸。

《亿级流量网站架构核心技术》中的「分层」强调:越靠近用户,容量越大、一致性越弱;越靠近数据,一致性越强、成本越高。 合成示例:CDN 可承载 19.6 万 QPS 的 85%(合成),Caffeine 再吸收 94% 的源站 QPS 的 miss 子集,Redis 仅处理约 1.2% 的绝对读量—— 但一旦百分比基数是亿级,1.2% 仍是十万级 ops,必须按 shard 热点建模而非按「平均 QPS」采购。

层级职责一致性失败时默认策略
CDN静态/半静态 HTML、图片、部分 JSON分钟级,版本 URLstale-if-error 有限使用
Caffeine热点对象、读多写少聚合进程内秒~分钟miss 走 Redis,禁止直打 DB 风暴
Redis共享缓存、互斥、计数TTL + 主动失效熔断后降级静态页/旧版本
DB权威数据强一致事务限流 + 排队,保护连接池

架构评审应强制每份缓存设计文档回答四个问题:key 谁生成、失效谁触发、穿透谁兜底、热点谁互斥。 缺任一项,事故窗口会在大促零点自动补全——通常以连接池打满的形式呈现。 与微服务拆分耦合时,还要声明跨服务 cache key 前缀与 BFF 聚合层是否二次缓存,避免同一语义在两层 Caffeine 重复存储却 TTL 不同。

写路径与读路径的不对称常被忽视:库存扣减、券领取走写 DB + 异步失效广播;读路径仍可能命中旧 Caffeine entry 800 ms。 不变量要求写路径发布版本戳或 canal 事件,读路径在 entry 上比对 maxVersion;否则「架构上允许多级缓存,业务上不允许任何旧值」会成为无解约束。 合成示例:活动价变更后 1 s 内 Redis 已更新,但 400 台应用的 Caffeine 仍可能各持有旧价 30 s——这是设计选择,不是 Caffeine bug。

框架训练 · 本地缓存

3Caffeine 本地缓存参数与一致性

Caffeine 3.x 在 JVM 内提供接近最优的命中率/内存曲线,但参数不是「maximumSize 越大越好」。 框架训练的核心是:把 Caffeine 当作 miss 过滤器,而不是第二份 Redis。 推荐显式设置 maximumWeight + Weigher 按对象字节加权,避免大 JSON 挤掉大量小热点; expireAfterWrite 与 Redis TTL 保持短于 Redis 至少一个数量级(例如本地 30 s、Redis 300 s), 使本地层先抖动、共享层仍稳定——这与许多团队「两层 TTL 相同求一致」的直觉相反,却是控制击穿窗口的常见做法。

refreshAfterWrite 适合读多写少且可容忍短暂旧值的场景:后台异步 refresh 不阻塞读线程,但不能用于库存、价格等强业务约束字段 unless 有版本校验。 合成示例:详情页「描述文案」可 refresh;「活动价」必须 write expire + 事件失效。 记录 stats(recordStats())并导出 hitRate、loadSuccess/loadFailure、eviction 到 Prometheus—— 没有 load 耗时分布,你无法区分「真 miss」与「load 函数被 DB 拖死」。

多实例一致性:Caffeine 无内置 pub/sub。常见模式为 Redis pub/sub 或 MQ 广播 invalidate(key); 大促前「全量 clear 本地缓存」是高风险操作——会在同一秒制造 miss 风暴,应改用版本前缀 item:v20250601:8810221 切换。发布系统滚动重启时,新旧实例会短暂持有不同版本 entry,必须在网关做灰度或接受展示层不一致。

常见陷阱

CacheLoader.load 内直接访问 DB 且无 per-key 互斥,会把 Caffeine 变成「并发击穿放大器」: 同一 key 的 200 线程同时 miss,会发起 200 次相同 SQL。必须在 loader 外包 synchronized(key)、 Striped lock 或迁移到 Redis SETNX 互斥。另一个陷阱:把 Feign 远程调用放进 loader——远程抖动时 load 超时堆积, 会占满 ForkJoinPool 或 boundedExecutor,引发级联超时。

条件化结论:当单 key 源站 QPS 估算 < 2000/s 且对象 < 4 KB,Caffeine + 互斥通常足够; 当单 key > 1 万/s,应把互斥上移到 Redis 或使用 singleflight 组件,本地缓存只缓存「已互斥后的结果」。 这与《亿级流量》中「本地缓存适用于读多写少、允许不一致」的表述一致,但要把「允许」量化成 SLA 秒数写进评审。

内存与 GC:Caffeine entry 持有强引用对象,超大 List 缓存会导致 Old Gen 晋升加速。 架构上应缓存 DTO 而非 ORM 实体,避免懒加载代理在 cache 外触发 N+1。 合成示例:一台 8 G 堆容器配置 Caffeine weight 上限 512 MB,约 2 万条详情 DTO——超出后 W-TinyLFU 淘汰非热点, 此时命中率下降 2% 可能对应 Redis ops 上升 30%(miss 乘数再次生效)。

3.1 Loader 线程池与超时预算

Caffeine load 应使用独立 boundedExecutor,线程数与 DB 连接池上限耦合:若 executor 队列 1000、连接池 200, 队列堆积会直接转化为获取连接超时。合成示例:load P99 800 ms 时,executor 队列深度 > 50 即应触发降级返回旧 Redis 值或静态兜底。 超时预算应小于网关 upstream timeout,否则客户端已 504 而 loader 仍在占连接——这是隐藏连接泄漏。

记录 eviction reason:size、expired、replaced 三类指标分开告警。size eviction 频繁说明 weight 过小; expired 频繁说明 TTL 与流量不匹配;replaced 频繁说明写路径更新过于激进。 与 GC 联动:Full GC 后 Caffeine 不一定清空,但 pause 会导致 load 超时尖刺,需在 GC log 时间轴对齐 miss 尖刺排查。

框架训练 · Redis 7.x

4Redis 7.x 分布式缓存与热点防护

Redis 7.x 在 Cluster 模式下通过 hash slot 分片;热点 key 仍可能打满单 shard,与「集群水平扩展」无关。 框架训练应掌握:cache-aside 标准流程、SET key value NX EX ttl 互斥重建、空值缓存防穿透、 TTL jitter 防雪崩、以及 Redis 7 的 CLIENT TRACKING 等高级特性在「谁该用」上的边界—— 多数电商详情页不需要 tracking,除非构建自定义 client-side cache 且能处理 redirect 风暴。

热点防护官方与社区常见方案包括:本地热点探测 + 短 TTL 副本 key、Redis Cell 限流模块(若可用)、 或在应用层对 hot key 做逻辑拆分(read replica key + 聚合)。 合成示例:将 item:8810221 拆为 item:8810221:slot-3 四个副本随机读,写入时 fan-out 更新—— 一致性成本上升,但单 shard ops 可降 60%(合成示例,需压测验证)。

// 互斥重建伪代码(Java,合成示例)
String val = redis.get(key);
if (val == null) {
  if (redis.set(lockKey, "1", "NX", "EX", 10)) {
    try {
      val = loadFromDb(key);
      redis.setex(key, ttlWithJitter(300), val);
    } finally {
      redis.del(lockKey);
    }
  } else {
    Thread.sleep(20);
    return redis.get(key); // 或短暂降级
  }
}

Redis 7.x 连接客户端需开启合理的 connection pool 与 timeout:failover 期间 MOVED/ASK 重试风暴常表现为 client CPU 高而非 server down。 使用 Lettuce 时区分 sync/async pipeline;大促读路径避免 pipeline 内混用不同 key slot 导致 Cluster 报错。 持久化策略(AOF rewrite)与 backup 任务不要与零点 TTL 过期重叠——合成示例中 AOF rewrite 与集体过期叠加时, shard 延迟 P99 从 2 ms 升至 18 ms,足以让互斥锁超时重入,形成「重建连环击穿」。

# redis-cli 7.x 热点 key 粗查(合成示例,生产用 scan + 采样)
redis-cli --hotkeys
redis-cli --latency-history -i 1
redis-cli INFO commandstats | grep get

内存淘汰:当 maxmemory-policy 为 allkeys-lru 时,集体过期与 LRU 淘汰叠加可能导致「非热点 key 被误杀、热点 key 仍过大」。 详情页 value 建议压缩(Snappy/LZ4)并控制 < 10 KB;超过 512 KB 的 JSON 应拆字段缓存。 书籍中强调的「Redis 是内存系统」在 7.x 仍成立:network bandwidth 与 single-threaded event loop 常先于内存触顶。

读写分离:只读副本可分担读,但复制 lag 下可能读到旧值;活动页若禁止旧价,读 master 或读前校验 version。 Sentinel/Cluster failover 的 RTO 与 client 重试策略必须在压测中量出:合成示例 RTO 800 ms 内若应用 retry 无 backoff, 会把 19.6 万 QPS 放大成 39 万次 Redis 命令——这是框架层必须禁止的默认 retry。

体系架构 · 边缘与键设计

5CDN 边缘缓存与缓存键设计

CDN 是多级缓存中容量最大、语义最薄的一层:它不理解 SKU,只理解 URL、Header 与 cache-control。 架构设计的首要任务是缓存键等价类:哪些 query 参数参与 cache key、哪些 Vary 头会分裂缓存、 活动态是否通过 path 版本化(/static/promo/20250601/banner.json)而非 query 开关。 合成示例:未剥离 utm_source 导致 cache key 爆炸,命中率从 92% 跌至 41%,回源带宽翻倍——这是键设计事故,不是 CDN 故障。

动态接口「CDN 缓存」需满足:响应可缓存、无 Set-Cookie 泄漏、错误码不缓存(或极短 negative cache)。 对商品 JSON 常用「短 TTL + stale-while-revalidate」:边缘返回旧 JSON 的同时异步回源,适合非交易读路径。 交易、库存、券状态必须 bypass CDN,或在 edge 做不可缓存标记并由 BFF 聚合静态与动态片段(ESI 思路,现代多用 CSR 拆分)。

图 2 · 读路径:CDN / 本地 / Redis / DB 命中顺序
读路径
CDN HIT 85% Caffeine HIT 94% of origin Redis 共享层 MySQL 权威源 MISS 乘数 每层只吸收 部分剩余流量
读图方式:自上而下为延迟从低到高;百分比为合成示例,用于说明「多层命中仍可能留下万级绝对 QPS 到 Redis/DB」。设计时应对剩余流量做绝对值估算,而非只看命中率。

回源保护:源站应配置 CDN 专用限流与 request coalescing;当 Redis 故障切换至 DB 降级时,应通过 CDN API 主动 purge 相关 URL, 避免边缘仍返回 200 包裹的 stale 业务 JSON。架构 15% 篇幅强调:CDN 与源站之间的契约是 HTTP 缓存语义,不是业务 DTO 契约—— 契约变更必须伴随 cache-control 评审,否则会出现「代码已回滚、边缘仍卖旧价」的合规风险。

国际化与多 region:不同 region CDN 缓存独立,活动配置需同步 purge 或低 TTL。 键设计还要考虑 A/B 实验桶:若 X-Exp-Bucket 进入 Vary,需评估 cache 碎片化是否可接受,或改为服务端渲染静态壳 + 客户端拉实验配置。

5.1 Purge 与活动切换 checklist

活动切换 SOP 应包含:bump 版本 path、Redis 预热、CDN purge tag、Caffeine version 切换四步顺序—— 顺序错误会出现「Redis 新、CDN 旧」或「CDN 新、本地旧」。合成示例:先 purge CDN 后预热 Redis,会在 30 s 内把回源 QPS 打到未经预热的源站, 属于可避免的流程事故。

HTTP 缓存头评审表应列出每个 API 的 cache-control、s-maxage、Vary、是否带 Set-Cookie。 对 JSON API 慎用 long max-age unless URL 版本化;对 HTML 壳可用 long cache + 内嵌 script 拉动态价。 边缘 WAF 与 bot 流量会改变 CDN 命中统计,需在分析命中率时剔除扫描流量,否则误判「缓存失效」。

框架训练 · 分诊核心

6击穿/雪崩/穿透分诊框架

值班话术必须先分诊再动手:击穿是单 hot key 过期/不存在导致并发 rebuild; 雪崩是大范围 key 同时失效或 Redis 不可用导致流量整体下沉; 穿透是恶意或异常查询不存在的数据,缓存永不命中。 三者症状可重叠——复合现场第 1 章即击穿 + 雪崩 + 回源,但 remediation 不同:击穿加互斥与热点副本,雪崩加 jitter 与集群隔离,穿透加布隆与空值缓存。

类型典型信号首要误判首选手段
击穿单 key ops 尖刺,DB 慢 SQL 同一 id「Redis 挂了」互斥 / singleflight / 热点拆分
雪崩多 shard CPU 同步升,TTL 对齐「扩容 Redis」TTL jitter、预热、隔离池
穿透miss 率升但 key 分散,404 业务「加内存」布隆、空值短 TTL、参数校验

框架训练要求把分诊写成决策顺序:先看 Redis slowlog 与 hotkey 是否集中;再看 key 过期时间分布是否对齐; 再看 DB 慢查询是否同一模板;最后看 CDN 回源是否随源站 5xx 上升。 合成示例:若 hotkey 集中且 DB SQL 带同一 primary key,击穿优先级高于「全库雪崩」叙事——此时扩容 Redis 分片无效。

防护击穿雪崩穿透
TTL jitter辅助必选无关
互斥锁必选辅助无关
空值缓存慎用辅助必选
本地缓存吸收 miss可能放大需布隆前置
CDN 缓存静态有效降源站 QPS无效
图 3 · 分诊决策流(值班首 5 分钟)
决策流
Redis 延迟/CPU 告警 单 key ops 尖刺? 大量 key 同时过期? TTL 分布 miss 分散 + 404? 击穿 SOP 雪崩 SOP 穿透 SOP 未分诊前禁止:全量 flush、无 jitter 重设 TTL、盲目扩容
读图方式:从顶向下分支;左支优先互斥与热点,中支优先 jitter/预热/隔离,右支优先布隆与空值。底部为全局禁令,防止「习惯性救急」放大事故。

SOP 文档应一页纸:每类事故列出「三分钟内可执行」与「需要变更审批」动作。 框架训练占全文 50%,本章是核心:要求工程师在口头复盘时能不看稿说出三分法与对应首选手段——这比背诵 Redis 命令更能通过架构面试与真实值班。

框架训练 · 权衡决策

7方案对比矩阵与决策树

没有银弹方案,只有在明确 SLA 与不一致窗口下的条件化选择。 下列对比用于架构评审快速对齐,数字为合成示例,不代表厂商 benchmark。

方案延迟一致性运维复杂度适用
仅 Redis集群内较一致QPS 中等、热点可控
Caffeine + Redis进程间最终一致读多写少、可版本戳
+ CDN 边缘最低最弱公开静态/半静态读
Redis + 读副本有复制 lagshard 热点、可旧值

适合多级缓存

  • 读 QPS > 5 万/s,80/20 流量集中
  • 业务声明秒级不一致可接受
  • 具备版本戳、失效广播与压测环境
  • 有 hot key 监控与互斥规范

不适合硬上

  • 强一致库存/券/价保无版本校验
  • 无 TTL jitter 与预热 runbook
  • 把 Caffeine 当唯一互斥层
  • CDN 缓存带 Cookie 的个性化页
架构推断

当大促峰值倍率 > 2.5× 且热点 SKU < 500 个时,推断应优先投资热点互斥 + TTL jitter + CDN 静态壳, 而非继续横向加 Redis 分片——因 shard 数增加不降低单 key 热度的 physics 上限。 该推断需用压测验证;若 hot key 可拆分且读允许副本 lag,再考虑 read replica key 方案。

决策树口语版:是否公开读?否 → 跳过 CDN。是否单 key > 1 万/s?是 → Redis 互斥 + 热点拆分。 是否多 key 同 TTL?是 → 立即加 jitter。是否查询不存在 id?是 → 布隆 + 空值。 四个「是/否」答完,方案基本收敛——这比比较「Caffeine vs Guava」更有工程价值。

与组织流程耦合:方案对比矩阵应进入 RFC 模板,与 RACI 绑定——谁批准 TTL 变更、谁执行 CDN purge、谁对价保负责。 《亿级流量》案例的价值在于把技术选项映射到「大促零点」时间线;本文矩阵是对该映射的可填写版本。

7.1 面试与评审中的表达模板

架构面试回答三级缓存时,推荐结构:先讲不变量,再讲一个复合事故,最后讲分诊与检查表——避免从 Caffeine API 讲起。 评审中对方案 say no 的条件:无 hot key 监控、无 TTL jitter 规范、无 CDN purge 责任人——三缺一则标记为「大促高风险」。

问题现场 · 可观测与演练

8监控、混沌演练与排障

可观测性不能只有 Redis used_memory 曲线。生产级面板应包含:Caffeine hitRate/load 耗时 P99、 Redis per-command latency、单 key ops TopN、DB 连接池等待时间、CDN 回源带宽与 5xx 比例—— 并在同一 Grafana row 上对齐时间轴,便于识别复合场景。合成示例:仅看 Redis CPU 会错过 Caffeine miss 尖刺先行 90 s 的前兆。

生产经验

大促前混沌演练应包含「人为对齐 100 个 key TTL 过期」与「单 hot key 删除」两类注入,观察 DB 连接池与互斥是否生效—— 而非只测 Redis 主从切换。演练窗口避开真实流量;回滚预案第一条是关闭实验 TTL 脚本,而非 restart 全部应用。 排障时保留 slowlog、一次完整 trace、以及 key 过期时间样本(TTL key 批量采样),用于事后写 postmortem。

告警阈值建议分层:P3 单 shard ops > 基线 3× 持续 2 min;P2 连接池等待 > 50 ms P99;P1 DB threads_running 超阈且 Redis 正常—— 后者常意味着击穿已穿透 Redis。On-call runbook 链接到第 6 章分诊图,避免值班临时搜文档。

日志规范:cache miss 日志禁止全量打印 key(采样 0.1%);必须带 shard、key hash、是否触发互斥等待。 合成示例:某次事故因 debug 打开 miss 全量日志,磁盘 IO 成为新瓶颈——属于「排障动作引入二次事故」。

客户端版本:滚动发布期间 Caffeine 与 Redis 客户端版本不一致可能导致序列化格式差异,表现为「Redis 有值但本地永远 miss」。 发布检查应包含 schema 版本与 cache key 前缀 bump。Redis 7.x ACL 变更若未同步到所有 sidecar,会出现间歇性 NOAUTH 被误判为穿透。

与容量规划衔接:每季度用最新流量倍率重算 miss 乘数绝对值,更新 Redis shard 与 DB 只读组规模。 混沌演练结论应反馈到第 7 章决策矩阵——若互斥锁等待 P99 > 100 ms,说明 hot key 仍过高,应业务侧重排期而非继续调 lock TTL。

网关层缓存与业务 Redis 集群隔离:限流模块应使用独立 Redis 或本地 token bucket,避免缓存 Cluster 抖动时限流一并失效。 合成示例:缓存故障时独立限流仍将 DB 连接池峰值压在基线 1.5 倍以内,这是「隔离池」原则的具体实现。

序列化与 value 大小:JSON 体积大,Protobuf 省内存;Caffeine 与 Redis 须统一序列化版本。详情页宜拆 key 存骨架与评论,降低 hot key 网络解码成本。

异步 refresh 仅适用于可 stale 读场景;成交价必须 version 校验。Consumer 侧需限流,否则 async refresh 在洪峰形成第二 DB 负载。

值班交接须含 hot key Top5、TTL 脚本变更单、CDN purge 记录、Caffeine 版本配置,避免下一班重复 flush 扩大事故。

读路径 miss 不宜绕 MQ 查 DB——应互斥短链回填。写路径削峰与读路径防击穿机制不可混谈。

安全:key 构造白名单、Redis ACL 前缀隔离、Caffeine 慎存 PII。缓存投毒与误 FLUSH 同属人为/恶意失效窗。

成本:CDN offload 与 Redis 内存需 ROI 测算,与 DB 扩容和带宽对比,推动业务接受 documented 不一致窗口。

Onboarding paper drill:用第 1 章时间线练分诊与前三动作;每季度更新合成 QPS 数字,机制描述保持稳定。

延迟双删与 cache-aside 并存时,顺序为删 Redis、改 DB、延迟再删 Redis;与 TTL jitter 互补而非替代。评审时应画时序图避免口头误解。

Redis 管道批量 GET 在活动页聚合接口可降 RTT,但需注意 Cluster 跨 slot pipeline 失败;宜按 slot 分组或使用 hash tag {sku}:detail:8810221 共 slot。

Hash tag 过度使用会导致 slot 倾斜——仅对 verified hot key 打 tag。监控应用 slot 分布 entropy,低于阈值时告警人工审查 tag 策略。

源站 nginx proxy_cache 可作为 CDN 失效后的第二边缘,减轻突发回源;TTL 须短于 CDN,避免三层旧值叠加。架构文档应标明各层 max stale 秒数。

再谈「框架训练」在本篇的含义:不是背诵配置项,而是在给定指标下能推导出该开互斥还是加 jitter。 例如给定单 key 12 万 ops/s、lock TTL 10 s、load P99 300 ms,可估算并发 rebuild 线程上界,从而判断 Caffeine 是否应前置 singleflight。

问题现场 35% 要求你能在 war room 复述因果链:运营脚本 → TTL 对齐 → 集体过期 → hot miss → DB 池满 → CDN 回源,而非只报「Redis CPU 高」。 架构 15% 要求你能画三层边界与不变量,并指出哪一层违反契约导致错价或旧库存展示。

与《亿级流量网站架构核心技术》配合阅读建议:先读缓存与高可用章节做标记,再读本文分诊与检查表,最后回到书中案例对照「若加 CDN 层会如何改变时间线」。 官方文档季度复查:Redis 7.x 对 expire 与 memory 的说明、Caffeine refresh 语义变更,应纳入架构组 changelog。

全链路灰度:新版本 cache key 前缀可在 5% 流量验证命中率与错误率,再全量 bump version;避免一次性 switch 造成全局 miss。 灰度期间对比旧前缀与新前缀 Redis ops,若新前缀 ops 高 2× 且错误率不变,多为预热不足而非代码缺陷。

Serverless 与冷启动:函数实例无 Caffeine,miss 直接打 Redis——弹性扩容时 miss 乘数随实例数线性增,更依赖 Redis 互斥与预热。 传统 JVM 服务与 Serverless 混部时,缓存策略不可同一套 RACI,需分服务类型写检查表条目。

商品池 BFF 聚合接口若缓存整包 JSON,热点活动页一次 miss 会 rebuild 含 40 个 sku 的大 value,Redis 网络与 GC 压力远高于单 sku key。 架构上应「可缓存单元最小化」:BFF 只缓存引用 id 列表,详情仍走单 sku 链——合成示例显示整包缓存 miss 时 P99 load 从 80 ms 升至 620 ms。

连接池公式粗算:若单次 SQL 50 ms,200 并发 rebuild 需 200 连接;池上限 320 时尚有余量,但叠加其它接口即触顶。 因此互斥的本质是把并发 rebuild 降为 1(或每 key 1),而不是提高 pool max——后者只会延后打满并拖垮 DB CPU。

最后补一段框架训练题:若监控仅告诉你 Caffeine 命中率从 94% 降到 91%,Redis ops 未变,是否可判定无击穿风险? 答案是否定的——若 miss 集中在 3 个 hot sku,绝对 miss QPS 仍可能翻倍;必须联查 hot key TopN 与 DB 同一 id 慢查询。 又若 Redis ops 升 30% 而命中率不变,可能是 value 变大或 pipeline 行为变化,而非流量涨——分诊必须多指标交叉,单指标结论一律降级为「假设」直至采样验证。

多级缓存评审最后一问:「大促零点若只能做一件事,做什么?」标准答案不是扩容,而是确保 hot key 互斥与 TTL jitter 已上线且演练过。 第二优先级是 CDN 活动 URL 版本化与 purge runbook 就绪。第三才是 Redis 分片与 DB 只读扩容——因为前两者决定失败窗口是否形成,后者决定窗口内能撑多久。 把此三优先级写进变更委员会 checklist,可减少凌晨「先加机器再说」的昂贵惯性。

排障命令最小集应写入 runbook 首页:TTL key 批量采样检测对齐、 INFO commandstats 看 get/mget 占比、CLIENT LIST 排查连接风暴、 MySQL SHOW PROCESSLIST 按 sql 指纹聚合。合成示例:某次 war room 在 12 分钟内执行了 4 次全库 flush, 每次 flush 制造新一轮 miss 尖刺——正确顺序是先定位 hot key 与 TTL 直方图,再决定是否局部 invalidate。 与 ES 日志篇「先数 shard 再扩容」同构:没有采样证据的 flush/restart 属于扩大失败窗口的应激动作。

演练验收应记录三项数值写入 postmortem 模板:hot key 删除后 DB 连接池峰值倍数、TTL 对齐组 vs jitter 组 DB QPS 差、 CDN 回源带宽峰值与源站 499 比例。缺任一字段的复盘不能关闭——否则下一季大促会重复「94% 命中率仍击穿」的错觉。

沉淀 · 检查表

9检查表、总结与延伸阅读

防击穿实战还涉及「读降级」产品语义:当 rebuild 超时时,是返回过期 Redis 值、静态占位图,还是直接 503? 三种选择对应不同客诉与合规风险,必须在 PRD 与架构 RFC 中预先绑定,而非事故中临时拍板。 合成示例:某团队选择返回 24 h 前 Redis 快照,价保投诉上升 0.05 个百分点——仍优于全站 503,但需法务预审。 降级开关应位于配置中心,支持按 sku 白名单关闭降级(爆款必须准价),长尾可容忍旧值。

SingleFlight 在 Go 标准库与 Java 生态均有实现;核心是同一 key 并发 miss 合并为一次 load。 与 Redis SETNX 互斥相比,SingleFlight 在进程内有效,跨进程仍需 Redis 锁或把 SingleFlight 放在独立「聚合缓存服务」。 大促架构常见模式:商品详情服务内 Caffeine + Redis 锁;列表页走独立 read-through 服务,避免列表 miss 拖死详情线程池。

Redis 7.x 内存碎片率在高 churn 场景上升,表现为 used_memory_rss 远大于 used_memory。 集体过期后大量 allocate/free 会加剧碎片,延迟抖动可能被误判为网络问题——应监控 mem_fragmentation_ratio 并在低峰 active defrag。 合成示例:fragmentation ratio 1.8 时,P99 延迟波动 ±5 ms,在互斥 lock 仅 10 s 的场景下足以导致重复 rebuild。

本地缓存与容器内存 limit:K8s memory limit 触顶 OOMKill 会瞬间清空 Caffeine,形成「rolling miss 风暴」。 limit 应基于 weight 上限 + 堆外 DirectBuffer + 线程栈留足 headroom;HPA 按 CPU 扩容无法解决单 key 热点,需 KEDA 或业务指标驱动。 书籍强调「限流保稳定」:在 cache 失效时,网关层 token bucket 限制到 DB 的 QPS 比「裸奔 rebuild」更能保整体可用性。

布隆过滤器误判率与穿透:布隆说「不存在」可跳过 DB,说「可能存在」仍需查 Redis/DB。 误判导致少量 extra DB 查询可接受;漏判不存在 id 则穿透。sku id 空间 10^9 级时,布隆 1GB 可控制在 1% 误判(合成参数,需按 id 规模重算)。 布隆不能替代参数校验:非法 id 应在 API 网关 reject,避免布隆 被随机 id 打满。

互斥锁粒度:锁 key 与 data key 分离,锁 TTL 略大于 P99 load 时间但小于 data TTL,避免死锁。 锁 value 用 uuid 并在 unlock 时 compare-and-del,防止误删他人锁。Redis 7.x 仍非强一致锁,极端 failover 可能双 holder—— 业务侧应 idempotent write 或 accept 短暂 duplicate load。金融级强一致应走 DB 行锁而非 cache 锁。

CDN 与 HTTP/2 多路复用:回源 storm 时 keep-alive 连接复用可降低 TCP 开销,但源站仍受限于应用线程。 回源 coalescing(合并相同 URL 的 in-flight 请求)在 nginx proxy_cache 或 Varnish 层常见,应用层也可用 request dedup map。 合成示例:coalescing 将 2000 并发相同 JSON 回源合并为 1 次 DB 查询,是击穿最后一道防线。

预热脚本反模式:大促前 5 min 全量 SCAN Redis 再 GET 灌缓存,会把 SCAN 本身变成 CPU 热点。 应用侧应用活动清单 CSV 驱动预热,按 QPS 限速 MSET。预热 key 的 TTL 必须 jitter,否则「预热修复雪崩、制造新雪崩」。

读写链路分离架构:写 master 读 slave 时,cache-aside 读 slave DB 可能写后读不到——应在写路径 invalidate Redis 并 optional 广播 Caffeine, 读路径仍可能 hit 旧 Caffeine。强一致读应走 master 或 version check。书籍案例中的「延迟双删」是另一种一致性问题域,与本文 TTL 击穿正交但常同时出现。

压测脚本应模拟真实 key 分布(Zipf),而非 uniform random——uniform 会高估命中率、低估 hot key 风险。 合成示例:Zipf s=1.1 下 top100 key 占 42% 流量,uniform 仅 1%——两种分布下 Redis shard 负载差异可达 20×。 压测报告必须附 key 分布图与 TTL 分布直方图,否则架构评审无法签核;大促前复测时仅对比 QPS 而不对比分布属于无效回归。

法规与审计:缓存旧价、旧库存若涉及标价法,需保留「展示价来源 version 与 timestamp」供审计。 技术实现可在 JSON 增加 _meta.cacheTier_meta.asOf,前端按 policy 决定是否展示「价格更新中」。 这不是性能优化,而是 composite 场景下避免「技术降级变合规事件」的必要设计。

多活与缓存:双活 region 各自 Redis,无同步时同一 sku 两 region 价可能不同——CDN geo 路由使问题用户可见。 要么 central Redis + 跨 region 延迟,要么 accept region 不一致并在 UI 声明。书籍多活章节与缓存章节应联读,避免单 region 方案生搬硬套。

故障演练验收标准示例:hot key 删除注入后 60 s 内 DB 连接池峰值不超过基线 1.5×;TTL 对齐注入后 jitter 组 vs 对照组 DB QPS 差 > 3× 即判定 jitter 无效需改脚本。 未达验收标准的大促窗口变更应被变更委员会 block——这是流程层对技术 SOP 的背书。

总结:亿级流量多级缓存的胜负手不在「有没有 Redis」,而在miss 乘数、TTL 时间相关性、分层不变量是否写进代码评审与值班 SOP。 复合现场几乎总是击穿、雪崩与 CDN 回源交织;先用第 6 章三分法分诊,再选用互斥、 jitter、布隆与边缘契约手段。 上线前用下列检查表逐项打钩,责任到人——空白项即大促零点待填的坑。带走三句话:命中率是百分比,miss 乘数才是绝对值;TTL 对齐是雪崩温床;CDN 回源往往是击穿后的放大器,而非独立根因。

检查项标准责任人
TTL jitter所有批量预热 key 必须随机偏移 10%~30%缓存 Owner
热 key 互斥Redis SETNX + 超时,禁止裸 DB loader服务 Owner
Caffeine 上限weight 上限与 GC 压测报告归档SRE
CDN 键query 白名单评审 + 活动 purge 脚本前端/网关
穿透防护布隆或空值策略覆盖异常 id 查询服务 Owner
混沌演练季度 hot key 删除 + TTL 对齐注入SRE
分诊 Runbook链接决策流图,On-call 可 5 min 内打开架构组
降级语义PRD 明确过期值/503/占位图优先级,法务预审产品 / 架构

延伸阅读建议先读书中缓存与高可用章节,再对照 Redis 7.x 与 Caffeine 官方文档更新参数语义。 若你来自 ES 日志架构背景,可把「分片 fan-out」直觉映射为「miss 乘数 fan-in」——二者都是「百分比好看、绝对值致命」的容量陷阱,应在评审中并列讨论。

最后强调:多级缓存是风险转移而非风险消失——不一致、旧值、旧价被从 DB 转移到边缘与进程内, 必须用业务 SLA 与检查表显式接受。完成第 9 章检查表打钩后,建议在预发环境做一次 TTL 对齐注入演练,再批准大促窗口变更。 架构委员会签字时应同时确认:击穿/雪崩/穿透三分法已纳入 On-call 培训,且至少一名值班能在 5 分钟内完成 hot key TopN 采样与 TTL 直方图导出—— 这是与《亿级流量》缓存章节对齐的最低能力线,而非「读过本文」的自证。

  1. BOOK《亿级流量网站架构核心技术》— 缓存、高可用与大促稳定性相关章节
  2. DOCRedis 7.x 官方文档 — Cluster、过期策略与内存管理
  3. DOCCaffeine 3.x Wiki — 权重、refresh 与 stats
  4. DOCCDN 厂商文档 — cache-control、purge API 与 stale 策略(按实际厂商版本)