[系统设计 - 缓存设计专题] 3. 缓存一致性
编辑
13
2026-07-11
缓存一致性
缓存一致性矛盾根源是: 主存储与副本缓存之间的同步无法原子化导致的,会出现缓存与数据库数据不一致。
如何设计缓存一致性方案
flowchart TD
A[开始] --> B{读多写多?}
B -- 读多 --> C{并发高?}
C -- 低 --> D0[旁路缓存]
C -- 一般 --> D1[旁路缓存+延时队列双删]
C -- 高 --> E[读穿透+直接同步删除缓存]
B -- 写多 --> F{是否强一致?}
F -- 是 --> G[读写穿透]
F -- 否 --> H{可靠性?}
H -- 高 --> J[写穿透]
H -- 低 --> K[写回]
使用缓存的场景
- 读多写少(绝大多数业务:配置、商品、用户信息、不怎么变化的数据)
- 写多读少/强数据可靠(订单、计费、设备实时状态、要求强一致)
- 最终一致(业务可容忍短暂不一致,绝大多数场景)
- 强一致(读写完成后缓存 DB 立刻同步,无脏读窗口)
处理缓存一致性的通用标准方案
Cache Aside(最常用,最终一致)
有两种路径: 1.先更新 DB,后删除缓存;2.先删除缓存,后更新 DB
路径一
更新路径
flowchart TD
a[更新主存储]
b[删除缓存]
a --> b
命中缓存路径
flowchart TD
a[请求数据]
c[返回缓存]
e[读取主存储]
f[更新缓存]
a-- 命中缓存key -->c
a-- 未命中缓存key -->e
e-->f
f-->c
优点:
- 并发冲突概率低
缺点:
- 幻读,在更新主存储与删除缓存之间的时候,可能触发命中缓存路线,读到了旧数据。但选择这条路线就表示可以接受一定时间内的不一致。
- 脏读,在删除缓存后
- 不适合高并发多场景,容易缓存击穿,删除缓存后可能一瞬间主存储被击穿。
路径二
- 删除缓存
- 更新主存储
缺点:
- 永久脏数据,如果删除缓存后,还没更新主存储,此时请求促使路线更新了缓存,则存在永久脏数据。
兜底措施
- 延时双删,延时(延迟窗口要大于查询线程「读缓存→查 DB→写缓存」 的完整耗时: 500ms-1000ms)再删除一次,确保不会产生永久的脏数据。
延时实现
- 业务线程延迟: 如果发生宕机,容易丢失操作。
- 异步延时队列(推荐):更新逻辑完成后发送消息到延迟队列中,可用性可靠性由队列保证。
FAQ
为什么不能只延时删除?
- 只延时删除在并发高情况下,导致该延时窗口里有大量脏读请求。
什么场景可以不使用延时双删?
- 读写串行化(有锁)
- 单线程操作数据
- 并发极低
读写穿透(强一致性)
这是一种数据代理方案,即使用 Cache 代理统一返回数据,能够保证数据强一致性。读写穿透是两条链路,一起配合使用才能保证强一致性。
必须保证读写路径一致,不被绕开,否则无法保证强一致。
Write Through
写操作直接同时更新缓存和主存储,缓存和数据库同写成功才返回。需要做临时事务隔离才行
更新路径
sequenceDiagram
participant App as 业务应用
participant Cache as 缓存
participant DB as 主存储
App -->> DB: 更新数据
App -->> Cache: 更新缓存(开启缓存事务提交)
alt 更新DB失败
DB -->> DB: 回滚数据
DB -->> App: 返回写入失败
else 更新DB成功
DB -->> App: 返回写入成功
end
优点:
- 保证了缓存与主存储的强一致性
缺点:
- WriteThrough 的事务隔离只作用于单个 key,多表、多个缓存 key 无法原子提交,复杂关联更新(订单 + 库存)必须加锁保证整体串行。
- 要增加临时隔离的事务屏蔽读,增加了复杂度。
- 如果要保证覆盖顺序,需要加锁。
Read Through
sequenceDiagram
participant App as 业务应用
participant Cache as ReadThrough缓存代理
participant DB as 主存储
App -->> Cache: query(key)
Cache -->> Cache: 查询缓存key
alt 缓存命中
Cache -->> App: 返回缓存数据
else 缓存未命中
Cache -->> DB: 查询原始数据
DB -->> Cache: 返回原始数据
Cache -->> Cache: 回填缓存 key=data
Cache -->> App: 返回缓存数据
end
优点:
- key未命中时,缓存组件自动回填获取最新数据,避免脏读和幻读。
缺点:
- 高并发时,缓存组件会阻塞所有请求。
- 若不搭配写穿透,使用 Cache-Aside或双写 方案在高并发时,仍会存在短暂窗口的脏数据。
高并发优化方案(解决并发的缓存击穿)
- 单 key 并发回填锁(最通用,互斥锁)
- 缓存占位预热
- 批量合并回填缓存:代理做请求队列聚合, 回填后notify all。
写回/异步刷盘
写只更新缓存,异步批量刷数据库
优点:
- 写入快
缺点:
- 缓存宕机数据丢失。
- 不可靠,金融类有风控的业务禁用。
- 0
- 0
-
分享