主题
概念卡片:缓存一致性
一句话机制:缓存一致性解决「缓存和 DB 数据不同步」——核心是写策略:主流 Cache-Aside(旁路缓存) 用「先更新 DB、再删缓存」保证最终一致;高并发下用双删 + 延迟队列、版本号或 CDC 兜住「删缓存前的并发读」窗口。
三种缓存模式
| 模式 | 写流程 | 特点 |
|---|---|---|
| Cache-Aside(旁路) | 更新 DB → 删缓存 | 主流,读未命中才加载缓存 |
| Write-Through(写穿透) | 更新缓存 → 缓存同步写 DB | 缓存层负责持久化,读不穿透 |
| Write-Behind(写回) | 更新缓存 → 异步落 DB | 写性能最高,宕机可能丢数据 |
关键代码示例(Cache-Aside)
java
// 读:未命中才加载
Data get(String key) {
Data d = cache.get(key);
if (d == null) { d = db.load(key); cache.put(key, d); }
return d;
}
// 写:先更新 DB,再失效缓存(顺序不能反)
void update(String key, Data newData) {
db.update(key, newData);
cache.delete(key);
}进阶一致性方案
| 方案 | 解决什么 |
|---|---|
| 双删 + 延迟队列 | 更新前先删一次,更新 DB 后再延迟删一次,兜住并发读旧值回填缓存的窗口 |
| 版本号 / 时间戳 | 缓存带 version,更新时校验,旧版本不回填 |
| CDC(订阅 binlog) | 订阅数据库变更日志,自动失效缓存,解耦业务代码 |
不变量(必须成立的约束)
- 先更新 DB 再删缓存,顺序不能反——反过来会「删了缓存、DB 没更新、并发读又把旧值写回缓存」。
- 双删的核心:第一次删「清旧值」,第二次延迟删「清并发读回填的旧值」,延迟时间略大于「读 DB + 回填」耗时。
- 一致性目标是最终一致,不要追求强一致(除非用版本号 + 事务)。
踩坑案例
- 现象:高并发下偶发缓存和 DB 不一致(缓存是旧值)。 原因:更新 DB 后、删缓存前,有并发读把旧值回填了缓存。解决:双删 + 延迟队列(或版本号 / CDC)。
常见误解
- 以为「先删缓存再更新 DB」更安全 → 恰恰相反,会在删后、更新前被并发读回填旧值。
- 以为缓存能强一致 → 缓存一致性本质是最终一致,强一致需版本号 + 事务,代价大。
关联
- 缓存选型:概念卡片:缓存技术选型与多级缓存
- Redis 一致性:概念卡片:Redis缓存与数据库一致性
- 源:
B40-资源/语雀-Code-Summary/Middleware/Cache&HighAvailability/CacheTheory/Java缓存一致性实践(31.6 KB)