Appearance
缓存问题与一致性
缓存雪崩、击穿与穿透
| 问题 | 表现 | 常见原因 | 应对思路 |
|---|---|---|---|
| 缓存雪崩 | 大量 key 同时失效,流量集中打到下游 | 批量设置同一 TTL、缓存集群故障 | TTL 加随机值、多级缓存、限流熔断、预热、高可用与降级 |
| 缓存击穿(热点 key) | 某个高并发热点 key 过期,瞬间大量请求回源 | 热点过期/被淘汰 | 互斥重建、逻辑过期+异步刷新、热点永不过期(配合更新) |
| 缓存穿透 | 请求的 key 在缓存和数据库都不存在,持续回源 | 恶意构造无效 key、参数错误 | 参数校验、缓存空值(短 TTL)、布隆过滤器、限流/风控 |
缓存击穿不是普通“缓存没有但数据库有”的单次未命中;其重点是高并发同时回源。布隆过滤器可能有假阳性但不应有假阴性(前提是正确构建),适合拦截大部分确定不存在的 key。
Cache Aside(旁路缓存)
这是最常用模式:
读路径
- 读取缓存,命中直接返回;
- 未命中则读取数据库;
- 将结果(或短 TTL 空值)写入缓存并返回。
写路径
- 先更新数据库;
- 再删除/失效缓存,而不是直接更新缓存。
先写数据库再删缓存可降低写入失败导致缓存提前失效的问题。删除操作应具备重试/补偿能力,可使用消息队列、binlog 订阅或定时校验处理异常。
并发不一致窗口
即使使用 Cache Aside,仍可能短暂读旧数据:写事务提交后、缓存删除前,读请求可命中旧缓存。另一个典型竞态是:
text
读请求:缓存未命中 → 读取旧数据库值 ───────────────┐
写请求: 更新数据库 → 删除缓存 │
读请求: 回填旧值常用缓解:
- 缓存设置 TTL,限制脏数据存活;
- 对关键 key 的回填做版本校验、互斥/单飞(singleflight)控制;
- 使用“延迟双删”作为概率性补偿:更新 DB → 删除缓存 → 延迟后再删一次。它不是严格一致性保证;
- 对强一致业务直接读主库/事务内数据,或采用事件驱动的版本化缓存与可靠消息。
其他缓存写策略
| 策略 | 含义 | 取舍 |
|---|---|---|
| Read Through | 缓存组件负责未命中时加载底层数据 | 对调用方透明,但需缓存层支持 |
| Write Through | 写缓存时同步写入底层存储 | 一致性较好,写延迟较高 |
| Write Behind / Write Back | 先写缓存,异步批量落库 | 写性能高,但有丢数据和最终一致性风险 |
不要脱离业务选择策略:商品展示可接受短暂最终一致;余额、库存扣减、权限变更等关键路径需要更严格的事务、条件更新、幂等和审计设计。