多级缓存一致性保证

390 阅读5分钟

1. 常见多级缓存结构

  • L1:本地缓存(如Java 中的 Caffeine):进程内缓存,速度最快,容量最小
  • L2:分布式缓存(如 Redis、Memcached):集群化缓存,供多节点,容量中等。
  • L3:数据库(如 MySQL):底层数据源,数据最终一致性的基准

2. 核心一致性难点

  • 更新顺序问题: 数据更新时,若先更新缓存再更新数据库,可能因为数据库更新失败而产生脏数据;若先更新数据库,可能因为缓存更新失败导致不一致。
  • 缓存穿透/穿透: 缓存未命中时大量请求直击数据库,或热 Key 失效瞬间引发数据库压力骤增。
  • 分布式环境下的并发问题: 多节点同时读写时,可能出现一个节点更新了缓存,另一个节点仍然读取旧缓存的情况。

3. 常见方案

方案一、Cache-Aside(缓存旁路模式)

以数据库为基准,缓存仅作为“旁路”辅助,不主动参与数据更新,适用于读多写少的场景。

  • 读操作
  1. 先查 L1 本地缓存,命中则返回
  2. 未命中则查询 L2 分布式缓存,命中则返回并同步到 L1
  3. 均未命中则查询数据库,返回数据并更新 L2 和 L1。
  • 写操作
  1. 先更新数据库
  2. 再删除 L2 和 L1 缓存(非更新)

优势: 避免缓存更新时的并发冲突问题(如两个写操作同时更新缓存导致数据不一致)

注意事项: 删除缓存可能失败,需配合”重试机制”(如消息队列异步重试)或“设置缓存过期时间“兜底。

方案二、Write-Through(写透模式)

写操作先更新缓存,再由缓存同步更新数据库,确保缓存与数据库同时修改。

  • 流程:
  1. 写操作直接更新 L1 缓存
  2. L1 缓存更新后,立即同步更新 L2 缓存
  3. L2 缓存更新后,再同步更新数据库
  4. 所有层级更新成功后,再返回更新成功。

优势: 缓存与数据库强一致性,适合对一致性要求极高的场景。

缺点: 写性能差(多了更新缓存的步骤),且任意一个环节失败会导致整个写操作失败。

方案三、Write-Behind(写回模式)

写操作先更新缓存,数据库异步更新(如批量延迟写入),牺牲一部分一致性换取高性能。

  • 流程
  1. 写操作直接更新 L1 和 L2 缓存,标记为“脏数据”
  2. 缓存系统定期(或达到阈值)异步将“脏数据”批量写入数据库
  3. 数据库写入成功后,清除”脏数据“标记。

优势: 写性能极高(减少数据库 IO 次数),适合高并发写的场景(如日志收集)

风险管理: 若缓存宕机,会导致未及时同步到数据库的“脏数据”丢失,需要持久化缓存(如 Redis 的 RDB/AOF)降低风险。

方案四、基于消息队列的异步一致性

通过消息队列解耦数据库与缓存的更新,确保更新操作不会丢失,适合分布式系统。

  • 流程
    • 写操作先更新数据库,成功后发送“数据更新消息”到消息队列
    • 消息队列消费者监听消息,依次删除/更新 L2 和 L1 缓存
    • 若缓存更新失败,消息队列重试(如 RabbitMQ 的死信队列机制)。

优势: 解决“数据库更新成功但缓存更新失败“的问题,保证最终一致性。

注意: 需处理“消息重复消费”(如给消息加唯一 ID 去重)。

4. 特殊场景补充策略

4.1 缓存与数据库并发更新冲突

当多个线程同时更新同一数据时,可能出现“旧数据覆盖新数据”的问题。

  • 解决方案
    • 给缓存增加版本号:每次更新版本号 + 1,更新缓存时检查版本号是否大于当前缓存版本,避免旧数据覆盖
    • 分布式锁:更新缓存前获取锁(如 Redis 的 SETNX),确保同一时间只有一个线程更新。

4.2 热点 Key 一致性

热点 Key(如秒杀商品库存)被高频访问,若缓存失效可能引发数据库雪崩。

解决方案

  • 互斥锁: 缓存失效时,只允许一个线程查询数据库并更新缓存,其他线程等待
  • 热点 Key 永不过期: 不设置过期时间,通过消息队列异步更新缓存(如库存变化时主动推送更新)
  • 本地缓存兜底: L1 本地缓存保留热点 Key 的“兜底值”,即使 L2 缓存失效,也能返回较新数据。

4.3 跨层级缓存同步延迟

L1 本地缓存为进程内缓存,多节点部署时,某节点更新数据后,其他节点的 L1 缓存可能仍为旧值。

解决方案

  • 缓存更新通知: 使用 发布 - 订阅 模式(如 Redis Pub/Sub),某节点更新缓存后,向其他节点发送“缓存失效通知“,触发其他节点删除本地 L1 缓存
  • 缩短 L1 缓存过期时间: 让 L1 缓存的过期时间远短于 L2(如 L1 10 秒,L2 1 小时),通过快速过期减少不一致窗口。

5. 一致性与性能的平衡原则

  • 不追求“强一致性”: 多级缓存的本质是”性能优化工具“,强一致性会牺牲性能,多数场景下保证“最终一致性”即可(如允许毫秒级/秒级的不一致)
  • 按业务场景选方案
    • 金融交易:优先使用 Write - Through + 分布式锁,保证强一致性
    • 电商商品详情:用 Cache-Aside + 消息队列,允许短时间不一致;
    • 日志 / 统计数据:用 Write-Behind,优先保证写入性能。
  • 兜底机制: 所有方案都需要配合“缓存过期时间”,即使更新失败,过期后也能从数据库加载新数据,避免脏数据永久留存。

总结

多级缓存一致性的核心是“明确业务对一致性的容忍度”,通过更新顺序控制+异步补偿+兜底策略实现平衡。