Mysql和Redis数据一致性总结

AI摘要
本文系统梳理了MySQL与Redis数据一致性的核心矛盾与主流解决方案。作为技术知识分享,文章首先指出旁路缓存模型存在固有并发时序漏洞,无法实现绝对实时强一致。随后详细对比了五种主流方案(标准删缓存、延迟双删、MQ异步、Binlog订阅、分布式锁)的原理、优缺点及适用场景,并指出两种错误方案。最后强调技术选型本质是性能与一致性的权衡,并给出了组合方案与兜底策略建议。

核心矛盾:MySQL 持久化强一致、Redis 高性能缓存,两者无法做到绝对实时强一致,只能根据业务选型:最终一致 / 准实时一致 / 强一致。

一、先说Redis缓存策略:旁路缓存模式(Cache-Aside)

流程

  • :先查缓存 → 缓存命中直接返回 → 未命中查数据库 → 写入缓存
  • 先更新数据库 → 再删除缓存(禁止更新缓存!)

也有其他缓存策略,但旁路缓存使用率最高。

旁路缓存(Cache-Aside)模型本身存在时序漏洞,属于架构固有问题,为了降低脏数据的危害,衍生出数据一致性方案

二、主流 5 种数据一致性方案(从简单到高可用)

方案 1:先更库,后删缓存【标准版旁路缓存(Cache-Aside)】

流程

  1. 业务修改 / 新增 / 删除 MySQL 数据
  2. 数据库事务提交成功后
  3. 异步 / 同步删除 Redis 对应缓存 key
  4. 后续查询:缓存失效 → 查库 → 回写缓存

优点

  • 实现简单、代码量少、性能高
  • 绝大多数读多写少业务标配(商品、用户资料、配置)

存在问题

读写并发脏数据

线程 A 查缓存失效 → 查库 (旧数据) ,线程 B 更新库 + 删除缓存,线程 A 把旧数据写入缓存 → 脏数据会一直存在,直到缓存过期。

兜底解决

  • 缓存统一加过期时间(天然兜底,脏数据自动过期)
  • 非核心业务直接容忍,误差秒级

方案 2:延迟双删策略【降低并发脏数据・准实时一致】

针对方案 1 的并发漏洞,企业常用优化

流程

  1. 删除缓存
  2. 更新 MySQL 数据库
  3. 业务休眠 / 延时(500ms~1s,根据业务 QPS 调整)
  4. 二次删除缓存

原理

第一次删:提前清空缓存;延时二次删:干掉并发请求写入的旧缓存数据。

优点

大幅降低并发导致的脏数据,一致性更强

缺点

  • 同步延时会拖慢接口;
  • 优化:改用MQ 异步延时删除,不阻塞主线程

本质仍是旁路缓存模型,只能大幅降低脏数据概率,延时删除清除了一些脏数据,如果线程A回填旧缓存时间晚于延时删除,仍有脏数据。

方案 3:MQ 异步删缓存【高并发・高性能・最终一致】

流程

  1. 业务更新 MySQL(本地事务保证落库)
  2. 发送「缓存删除消息」到 RocketMQ/Kafka/RabbitMQ
  3. 消费者消费消息,异步删除 Redis key

核心优势

  1. 解耦:数据库操作与缓存操作拆分
  2. 削峰:高写并发不阻塞接口,异步处理
  3. 可靠:MQ 持久化 + 重试,杜绝缓存漏删
  4. 完美支持延迟双删(延时消息队列)

适用场景

电商订单、营销活动、高并发写入业务

兜底

MQ 消费失败:死信队列 + 定时任务补偿

本质仍是旁路缓存模型,只能大幅降低脏数据概率,优化了延时双删的问题,如果线程A回填旧缓存时间晚于延时删除,仍有脏数据。

方案 4:Binlog 订阅同步【无侵入・强兜底】

核心组件

MySQL Binlog + Canal / Debezium + 消费服务 + Redis

流程

  1. MySQL 数据变更自动记录 Binlog
  2. Canal 监听 Binlog 日志,模拟从库拉取变更
  3. 解析 insert/update/delete 事件
  4. 自动删除 / 更新 Redis 缓存

优点

  1. 业务代码零侵入,不需要手动写删缓存代码
  2. 全局兜底,防止代码漏删、bug 导致缓存残留
  3. 适合老旧项目、多服务共用同一库场景

缺点

时效性稍弱(秒级延迟),不适合强实时业务

本质仍是旁路缓存模型,只能大幅降低脏数据概率,只是优化了方案,如果线程A回填旧缓存时间晚于删除,仍有脏数据。

方案 5:分布式锁 + 串行化【强一致・低并发】

针对金额、库存、账户等零容忍脏数据场景

流程

  1. 读写该数据前,先加分布式锁(Redisson)
  2. 同一 key 的读写请求串行执行
  3. 写:更库 → 删缓存 → 释放锁
  4. 读:查缓存 → 无则查库写缓存

优点

完全杜绝并发脏数据,最高一致性

缺点

并发能力下降,锁竞争影响性能,只适合低频核心数据

完全杜绝旁路缓存出现的并发脏数据,但是并发能力下降,锁竞争影响性能。

三、两种错误方案(禁止使用)

  1. 先删缓存,再更新数据库

脏数据出现概率远高于旁路缓存,高并发下必出脏数据,生产绝对禁用。

  1. 更新数据库 + 更新缓存

多写并发互相覆盖,缓存永久错乱,内存浪费严重。

四、三大特殊场景兜底方案

1. 缓存超时兜底

所有缓存 key必须设置过期时间(5 分钟~24 小时)哪怕代码异常、MQ 挂掉、Binlog 延迟,脏数据也会自动清理,是最终防线。

2. 定时任务全量刷新

低频次定时任务(凌晨 / 每小时),批量清理热点缓存、重置全量数据,修复长期不一致。

3. 读写分离场景

写主库、读从库:从库同步存在延迟,不要写完立刻读从库 + 写缓存;优化:核心数据读主库,或延长缓存过期时间。

五、生产环境最终选型

  1. 普通业务(用户 / 商品 / 资讯)

先更库 + 简单删缓存 + 缓存过期兜底

  1. 中高并发、要求数据较实时

先更库 + MQ 异步延迟双删

  1. 老旧系统、多服务共用库

业务删缓存 + Canal 监听 Binlog 二次兜底

  1. 资金 / 库存 / 交易核心数据

分布式锁 + 串行读写 + 弱化缓存(优先查库)

六、极简对比总结

方案 一致性 性能 复杂度 适用场景
先更库后删缓存 弱一致 极高 绝大多数业务
延迟双删 准实时一致 读写并发较高
MQ 异步删缓存 最终一致 高并发写业务
Binlog 同步 最终一致 无侵入兜底
分布式锁 强一致 核心金融数据

七、终极极简总结

  1. 分布式锁外,所有缓存数据一致性方案,都无法彻底避免慢读请求延迟回填导致的脏数据;
  2. 延迟双删、MQ 延时、Binlog 兜底,核心作用是降低脏数据触发概率、缩短脏数据生命周期、补偿漏删场景
  3. 旁路缓存(Cache-Aside)模型本身存在时序漏洞,属于架构天生的时序缺陷,无法通过单纯删缓存彻底解决;
  4. 技术选型本质是取舍:
    • 要高性能 → 容忍最终一致、小概率脏数据
    • 要强一致性 → 牺牲并发,加锁串行

八、组合方案

  1. 常规:先更新 DB、再删缓存
  2. 兜底 1:所有 key 必加过期时间(底线)
  3. 兜底 2:Canal 监听 Binlog 异步二次删缓存
  4. 特殊热点:热点 key 本地标记、永不过期、后台定时主动刷新

不加锁、不串行、高性能、脏数据概率极低

本作品采用《CC 协议》,转载必须注明作者和本文链接
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 1

今夕是何年,现在还能看到这种文章,有种恍如隔世的感觉

1天前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!