分布式缓存一致性:从核心争议到企业级解决方案

分布式缓存一致性:从核心争议到企业级解决方案

分布式缓存一致性是高并发架构中最经典的难题之一。它的本质在于:数据库(如 MySQL)和缓存(如 Redis)是两个独立的系统,我们无法通过单一的本地事务来保证它们同时操作成功或同时失败。

当引入并发读写和网络延迟时,数据不一致的风险就会急剧放大。要彻底理解它,我们需要剖析在数据发生变更时,处理数据库和缓存顺序的几种方案及其引发的并发问题。

一、 核心争议:更新还是删除?先操作谁?

当业务数据发生更新时,我们面临两个基本选择:

1. 为什么推荐"删除缓存"而不是"更新缓存"?
  • 更新缓存的缺点 :如果并发有多个写请求,容易发生并发写覆盖。例如,线程 A 和 B 先后更新数据库,但在网络抖动下,B 可能比 A 先更新了缓存,导致缓存中保留了 A 的旧数据。此外,如果该缓存数据是一个复杂计算的结果,频繁更新会浪费大量 CPU 资源(且更新后可能很久没人读)。
  • 删除缓存(延迟加载) :数据更新时直接将缓存失效。只有当下一个读请求到来时,才去数据库查询并重新构建缓存。这是一种"懒加载"思想,能有效避免并发覆盖和计算资源浪费。
    因此,业界普遍确立了**"操作 DB + 删除缓存"**的基调。
2. 为什么推荐"先更 DB,再删缓存"?

我们来推演一下并发场景下的两种顺序:
方案 A:先删缓存,再更新 DB

这种方案会导致严重的脏数据问题。

  1. 线程 A 准备更新数据,先删除了缓存
  2. 线程 B 此时来读取数据,发现缓存为空,去数据库查到了"旧数据"
  3. 线程 A 执行数据库更新完成。
  4. 线程 B 将刚才查到的旧数据写入了缓存
    结果 :数据库已经是新值,但缓存中永远是旧值,直到缓存过期。这就是为什么通常需要配合"延迟双删"来弥补。
    方案 B:先更新 DB,再删缓存
    这是业界推荐的标准模式。
  5. 线程 A 更新数据库。
  6. 线程 A 删除缓存。
    理论上,它也有一种极小概率的脏数据场景:
  7. 缓存刚好失效。
  8. 线程 B 读数据库,拿到旧值。
  9. 线程 A 更新数据库,并删除缓存。
  10. 线程 B 将旧值写入缓存。
    为什么说极小概率? 因为第 4 步(写内存)的速度必须慢于第 3 步(写磁盘+删内存)的速度,这在实际工程中极难发生。

二、 企业级一致性解决方案全景图

根据业务对"数据一致性"容忍度的不同,通常有以下几套标准打法:

1. 最终一致性(高性价比选项)

方案:Cache-Aside (先更 DB,再删缓存) + 缓存设置合理的 TTL(过期时间)

  • 适用场景:绝大多数非核心计费的互联网业务(如商品详情、文章内容、用户公开信息)。
  • 原理:利用 TTL 作为终极的"兜底"机制。即使因为网络抖动导致删除缓存失败,只要到了 TTL 时间,缓存自然失效,下一次读取一定会加载最新数据。
2. 准实时强一致性(解耦与重试)

方案:Binlog + MQ 异步清理

  • 痛点解决:在 Cache-Aside 中,如果"更新 DB 成功,但立刻断网导致删缓存失败"怎么办?
  • 架构设计
    1. 业务代码只负责更新 MySQL。
    2. 利用中间件(如 Alibaba Canal)伪装成 MySQL 从节点,监听 MySQL 的 Binlog 变更日志。
    3. Canal 解析出变更事件后,推送到消息队列(Kafka/RabbitMQ)。
    4. 独立的缓存同步服务消费 MQ,执行 Redis 的删除操作。
  • 优势:业务代码无侵入;即使删除 Redis 失败,MQ 的 ACK 机制会自动重试,确保缓存最终一定被清理。
3. 严格强一致性(高昂的性能代价)

方案:读写串行化 / 分布式读写锁

  • 适用场景:对一致性要求达到金融级,绝不容忍哪怕 1 毫秒的脏数据(例如:库存扣减、余额查询)。
  • 原理
    • 读锁(共享锁):只要没有线程在修改数据,大家都可以并发读取缓存。
    • 写锁(排他锁):一旦有线程需要更新数据,写锁会阻塞所有的读请求,直到数据库更新完毕且缓存被清理,才允许新的读请求进来。
  • 代价:极大地牺牲了并发性能。在真正的金融系统中,这种场景通常会直接放弃缓存,强打数据库主库。

三、 总结

处理分布式缓存一致性,本质上是在做**"可用性(性能)""一致性"**之间的权衡(CAP 定理的延伸):

  1. 如果没有极端的严格要求,先更新 DB,再删除缓存,并配上 TTL 是最稳妥的基石。
  2. 如果追求高可用且不想在业务代码里写重试逻辑,上 Canal + MQ
  3. 不要轻易在读多写多的高并发场景下使用分布式锁来强保一致性,那会让你引入缓存带来的性能优势荡然无存。
相关推荐
进击的_鹏8 小时前
从零开始的 Redis 学习
服务器·数据库·c++·redis·缓存
摇曳的精灵10 小时前
前端缓存优化
前端·缓存·缓存优化
见山是山-见水是水13 小时前
拆解网络缓存策略设计:原生鸿蒙页面的实现路径与调试方法
http·缓存·华为·harmonyos
T011561814 小时前
艺培场馆课时预约小程序用户端 & 后台联动踩坑上篇(预约、课时、缓存、消息、学员端自身问题)
java·缓存·小程序·bug·全栈项目实战手记·艺培课时系统‘’
北漂燕郊杨哥15 小时前
npm 缓存占满 C 盘?三步把它迁到 D 盘(附验证与避坑)
缓存·npm·node
克里斯蒂亚诺更新16 小时前
win下如何将redis自启动
数据库·redis·缓存
晓晓_za8986681 天前
Geo 优化服务 CI/CD 流水线搭建:源码自动构建、测试与灰度发布
运维·服务器·tcp/ip·spring·缓存·ci/cd
nvd111 天前
LiteLLM 与 Redis 响应缓存机制:从精确哈希匹配到模型底层 KV-Cache 边界
redis·缓存·哈希算法
名字还没想好☜2 天前
Next.js 用 cookies()/headers() 读请求信息:动态渲染触发、缓存失效与在 Server Action 里读写 cookie
前端·javascript·缓存·react·next.js·app router