【Java 开发日记】MySQL 与 Redis 如何保证双写一致性?

【Java 开发日记】
MySQL 与 Redis 如何保证双写一致性?(2026 年主流实践版)

在真实生产环境中,“双写一致性”几乎从来没有做到过强一致性(事务级原子性),绝大多数公司最终追求的都是最终一致性 + 可接受的不一致窗口。

下面按一致性强度从弱到强 + 复杂度从低到高排序,列出目前(2026年)最主流的几种方案,以及它们的适用场景、优缺点、残留风险和真实落地建议

1. Cache-Aside(旁路缓存) + 先更新 DB,再删缓存(目前 80%+ 公司默认首选)

写流程:
1. 更新 MySQL
2. 删除 Redis key(不管成功与否都返回成功)

读流程:
1. 先读 Redis,命中 → 返回
2. 未命中 → 读 MySQL → 回写 Redis → 返回

为什么删缓存而不是更新缓存?

  • 更新缓存需要把整个对象序列化回去(复杂对象特别麻烦)
  • 并发场景下“谁后写谁覆盖”问题很难解决
  • 删除更简单,失败了靠过期兜底

残留风险 & 概率排序(从高到低)

风险场景概率(日常)不一致窗口解决/缓解办法
更新 DB 成功,删 Redis 失败★★★☆☆直到 key 过期加重试 + 记录失败 key 到延迟队列重试
读写并发(经典脏读案例)★★☆☆☆几百 ms ~ 几秒延迟双删(见方案2)
主从读写分离 + 从库延迟★★☆☆☆主从延迟时间延迟双删 + sleep 时间包含主从延迟
极端网络抖动/Redis 挂了★☆☆☆☆永久(直到重启)监控 + 告警 + 降级读 DB

2026 真实建议
绝大多数中型项目就只用这个 + 合理过期时间(1~30分钟,根据业务热点调整)已经够用。
加一个“删缓存失败重试表/队列” 就能覆盖 99% 的场景。

2. Cache-Aside + 延迟双删(最常见的加强版)

写流程:
1. 删除 Redis(第一次删)
2. 更新 MySQL(事务提交)
3. sleep(500ms ~ 2s)   // 经验值:读接口平均耗时 + 主从延迟预估
4. 再次删除 Redis(第二次删)

为什么能大幅降低脏数据概率?

第二次删把“读-写-读”并发中,读请求在第一次删后、MySQL提交前回写的旧值删掉了。

缺点

  • sleep 时间很难精确(主从延迟抖动、网络抖动)
  • 增加了写接口的响应时间

2026 真实做法
sleep 时间一般设 500ms ~ 1500ms,或者改成发延迟消息到 MQ(推荐):

// 伪代码
@Transactional
public void updateUser(UserVO vo) {
    // 1. 先删(可选)
    redis.delete(key);

    // 2. 更新 DB
    userMapper.update(vo);

    // 3. 发延迟消息(RocketMQ / Kafka 延迟队列)
    rocketMQTemplate.sendDelayMsg("delay-delete-topic", key, 1000); // 延迟1秒
}

// 消费者收到消息后再删一次
@RocketMQMessageListener(topic = "delay-delete-topic")
public void onDelayDelete(String key) {
    redis.delete(key);
}

这样写接口不阻塞,还更可靠。

3. 基于 Binlog / CDC 的异步更新(目前大厂主流终极方案)

代表组件:Canal + MQDebeziumMaxwell阿里云 DTS

流程:

  1. 业务只更新 MySQL(不碰 Redis)
  2. MySQL Binlog 订阅 → 解析变更 → 发 MQ(或直接推 Redis)
  3. 消费者收到变更 → 删除/更新对应 Redis key

优点

  • 业务代码几乎无侵入(不用关心缓存)
  • 天然支持读写分离、主从延迟
  • 最终一致性窗口很小(通常秒级)

缺点

  • 引入额外中间件,运维复杂度↑
  • Binlog 订阅有延迟(几十 ms ~ 几秒)
  • 需要处理幂等、顺序消费、断点续传

2026 主流组合推荐

规模推荐方案备注
小中型Cache-Aside + 延迟消息双删性价比最高
中大型先更新 DB + 删缓存 + 失败重试加个重试表或死信队列
大型/高要求Canal / Debezium → RocketMQ/Kafka → 消费删缓存最稳,业务无感
极致强一致Rockscache / 分布式事务 + 标记删除极少数金融、对账场景

快速决策表(2026 年版)

业务特点推荐方案一致性级别复杂度
读多写少、允许几秒不一致Cache-Aside + 先更新DB再删缓存 + 过期最终一致★☆☆☆☆
写比较频繁,有脏读投诉延迟双删(sleep 或延迟MQ)最终一致(更好)★★☆☆☆
主从读写分离严重延迟双删 + MQ延迟消息最终一致★★★☆☆
要业务无侵入、规模很大Binlog 订阅 → MQ → 删/更新缓存最终一致(秒级)★★★★☆
金融级、对账必须几乎零差错Rockscache / 2PC + 版本号 / 标记删除接近强一致★★★★★

一句话总结(最实用版):

大多数项目:先更新数据库 → 删缓存 + 合理过期时间 + 删失败重试
进阶一点:把第二次删改成发延迟消息
再往上走:直接订阅 Binlog 做异步更新,业务代码彻底不管缓存

你当前项目属于哪一档?是已经有脏数据投诉了,还是想提前做预防?可以告诉我更多场景,我帮你选最合适的落地方案。

文章已创建 3996

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部