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

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

分布式缓存一致性是高并发架构中最经典的难题之一。它的本质在于:数据库(如 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. 不要轻易在读多写多的高并发场景下使用分布式锁来强保一致性,那会让你引入缓存带来的性能优势荡然无存。
相关推荐
爱丽_2 小时前
大型系统构建与性能优化:缓存、负载均衡、分库分表与会话方案
jvm·缓存
Rsun0455112 小时前
Redis中实现访问量计数
数据库·redis·缓存
ok_hahaha19 小时前
java从头开始-黑马点评-商户查询缓存
java·spring·缓存
新缸中之脑20 小时前
Google TurboQuant 详解
数据库·redis·缓存
DJ斯特拉1 天前
黑马点评技术汇总(五)缓存更新策略
缓存
XiYang-DING1 天前
【Java SE】缓存池和常量池的区别
java·spring·缓存
爱敲代码的菜菜1 天前
【Redis】Redis基本操作
java·数据库·redis·缓存·hash·zset
雾喔1 天前
redis简单命令
数据库·redis·缓存