先把结论放前面:缓存是放大器,不是保险
缓存放大的是「读性能」,也放大「不一致与故障」:命中的 1000 QPS 很爽,失效瞬间回源 1000 QPS 可能直接打垮数据库。所以设计缓存体系的第一原则是:先想清楚失效与回源,再想命中率。
第一问:要不要缓存?(很多人跳过了这步)
- 命中率上不去的场景不要加:写多读少、数据变化极快(如实时行情秒级变动),缓存只会引入不一致。
- 单机内存就够的场景别上分布式缓存:减少一个组件就少一种故障。
- 加缓存前先压测数据库:多数系统是慢 SQL 和缺索引,不是缺缓存。
多级缓存怎么搭(三级模型)
进程内缓存(Caffeine/本地 Map) ~50ns–1μs ← 挡热点与极高 QPS
↓ miss
分布式缓存(Redis) ~0.1–1ms ← 挡绝大多数回源
↓ miss
数据库 / 上游服务 ← 只允许极少量到达
- 本地缓存只放真正的热点(如大促商品),设短 TTL 或逻辑过期;别把全量数据塞进程内,会导致各实例数据各自为政。
- 分布式缓存承载常规流量:key 设计带业务前缀 + 版本号(上线改 key 结构时不必清库)。
失效策略:TTL 的三个设计点
- TTL 加随机抖动(基础值 ±20%):从源头避免「同生同死」的批量失效(雪崩的直接诱因,见故障复盘)。
- 热点 key 逻辑过期:不设物理 TTL,由后台任务主动刷新,读到旧值也不回源(数据允许秒级陈旧时)。
- DB 变更主动失效:写路径删缓存(Cache Aside),而不是更新缓存——避免并发写造成的中间态写脏。
四大经典故障的防线(每个都要单独防)
| 故障 | 现象 | 防线 |
|---|---|---|
| 雪崩 | 大量 key 同时失效,回源打垮 DB | TTL 抖动 + 多级缓存 + 回源限流 |
| 穿透 | 查不存在的 key,永远打 DB | 空值短缓存 + 布隆过滤器 |
| 击穿 | 单个热点 key 失效瞬间并发回源 | 单飞锁(同 key 只允许一个请求回源) |
| 热 key | 单 key 超高 QPS 打爆单节点 | 本地缓存兜底 + key 分片/多副本 |
踩坑记录:一次活动把「单飞锁」做成了「全局限流」,热点 key 请求排队反而把正常流量也拖慢。单飞锁必须按 key 维度(分布式锁 + key 粒度),而不是全局一把锁。
一致性:接受「缓存最终一致」,但要控制窗口
强一致(DB 与缓存实时一致)在分布式下代价极高。务实做法:
- 明确业务允许的陈旧窗口(秒级/分钟级),写进方案;
- 写路径 Cache Aside + 删除重试(消息队列兜底);
- 定时对账任务修正极少数脏数据——比追求绝对一致便宜一个量级。
落地清单
- 先问「要不要缓存」:命中率、数据变化频率、单机是否够
- 三级结构明确每级职责,本地缓存只放热点
- TTL 随机抖动;热点 key 逻辑过期 + 异步刷新
- 雪崩 / 穿透 / 击穿 / 热 key 四道防线各就各位
- 写路径 Cache Aside,删除失败有重试
- 缓存命中率、回源 QPS、单 key 并发监控告警
延伸阅读:上章 · 业务架构拆解 · 缓存雪崩故障复盘 · Redis 工具行