分布式缓存一致性:从核心争议到企业级解决方案
分布式缓存一致性从核心争议到企业级解决方案分布式缓存一致性是高并发架构中最经典的难题之一。它的本质在于数据库如 MySQL和缓存如 Redis是两个独立的系统我们无法通过单一的本地事务来保证它们同时操作成功或同时失败。当引入并发读写和网络延迟时数据不一致的风险就会急剧放大。要彻底理解它我们需要剖析在数据发生变更时处理数据库和缓存顺序的几种方案及其引发的并发问题。一、 核心争议更新还是删除先操作谁当业务数据发生更新时我们面临两个基本选择1. 为什么推荐“删除缓存”而不是“更新缓存”更新缓存的缺点如果并发有多个写请求容易发生并发写覆盖。例如线程 A 和 B 先后更新数据库但在网络抖动下B 可能比 A 先更新了缓存导致缓存中保留了 A 的旧数据。此外如果该缓存数据是一个复杂计算的结果频繁更新会浪费大量 CPU 资源且更新后可能很久没人读。删除缓存延迟加载数据更新时直接将缓存失效。只有当下一个读请求到来时才去数据库查询并重新构建缓存。这是一种“懒加载”思想能有效避免并发覆盖和计算资源浪费。因此业界普遍确立了**“操作 DB 删除缓存”**的基调。2. 为什么推荐“先更 DB再删缓存”我们来推演一下并发场景下的两种顺序方案 A先删缓存再更新 DB这种方案会导致严重的脏数据问题。线程 A 准备更新数据先删除了缓存。线程 B 此时来读取数据发现缓存为空去数据库查到了“旧数据”。线程 A 执行数据库更新完成。线程 B 将刚才查到的旧数据写入了缓存。结果数据库已经是新值但缓存中永远是旧值直到缓存过期。这就是为什么通常需要配合“延迟双删”来弥补。方案 B先更新 DB再删缓存这是业界推荐的标准模式。线程 A 更新数据库。线程 A 删除缓存。理论上它也有一种极小概率的脏数据场景缓存刚好失效。线程 B 读数据库拿到旧值。线程 A 更新数据库并删除缓存。线程 B 将旧值写入缓存。为什么说极小概率因为第 4 步写内存的速度必须慢于第 3 步写磁盘删内存的速度这在实际工程中极难发生。二、 企业级一致性解决方案全景图根据业务对“数据一致性”容忍度的不同通常有以下几套标准打法1. 最终一致性高性价比选项方案Cache-Aside (先更 DB再删缓存) 缓存设置合理的 TTL过期时间适用场景绝大多数非核心计费的互联网业务如商品详情、文章内容、用户公开信息。原理利用 TTL 作为终极的“兜底”机制。即使因为网络抖动导致删除缓存失败只要到了 TTL 时间缓存自然失效下一次读取一定会加载最新数据。2. 准实时强一致性解耦与重试方案Binlog MQ 异步清理痛点解决在 Cache-Aside 中如果“更新 DB 成功但立刻断网导致删缓存失败”怎么办架构设计业务代码只负责更新 MySQL。利用中间件如 Alibaba Canal伪装成 MySQL 从节点监听 MySQL 的 Binlog 变更日志。Canal 解析出变更事件后推送到消息队列Kafka/RabbitMQ。独立的缓存同步服务消费 MQ执行 Redis 的删除操作。优势业务代码无侵入即使删除 Redis 失败MQ 的 ACK 机制会自动重试确保缓存最终一定被清理。3. 严格强一致性高昂的性能代价方案读写串行化 / 分布式读写锁适用场景对一致性要求达到金融级绝不容忍哪怕 1 毫秒的脏数据例如库存扣减、余额查询。原理读锁共享锁只要没有线程在修改数据大家都可以并发读取缓存。写锁排他锁一旦有线程需要更新数据写锁会阻塞所有的读请求直到数据库更新完毕且缓存被清理才允许新的读请求进来。代价极大地牺牲了并发性能。在真正的金融系统中这种场景通常会直接放弃缓存强打数据库主库。三、 总结处理分布式缓存一致性本质上是在做**“可用性性能”与“一致性”**之间的权衡CAP 定理的延伸如果没有极端的严格要求先更新 DB再删除缓存并配上 TTL是最稳妥的基石。如果追求高可用且不想在业务代码里写重试逻辑上Canal MQ。不要轻易在读多写多的高并发场景下使用分布式锁来强保一致性那会让你引入缓存带来的性能优势荡然无存。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2459846.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!