🚀 一、为什么需要最终一致性?
分布式系统中常见业务:
- 创建订单 → 扣库存
- 支付成功 → 发货
- 余额扣减 → 写流水
- 会员开通 → 发券 → 发短信
这些动作不在同一个数据库,也不是同一个服务,要么都成功,要么都失败。
但传统的分布式事务(2PC/XA)太重量级,性能差、可用性差,于是就有了:
最终一致性:允许暂时不一致,但最终达到一致。
RocketMQ 的定位非常明确:
- 不做强一致
- 也不保证实时
- 但要确保最终一定落一致(不丢、不乱、不重复)
🚀 二、RocketMQ 如何保证最终一致性?(四种主流方式)
我按照真实工程实践,分四类说。
⭐ 方式一:事务消息(RocketMQ 标准方案)
RocketMQ 原生支持“两阶段提交 + 回查机制”。
流程(非常重要):
✔ 1)阶段一:发送 Half Message(预备消息)
Producer 调用:
sendMessageInTransaction()
Broker 收到消息,但不会投递给消费者,它处于 half 状态。
✔ 2)阶段二:执行本地事务
Producer 的业务逻辑:
- 下单写入数据库
- 扣减余额
- 记录流水
✔ 3)阶段三:根据结果返回 commit or rollback
Producer 返回:
- commit → Broker 标记 half message 为可消费
- rollback → 直接丢弃
✔ 4)Broker 会定时回查 Producer
如果 Producer 宕机 / 网络异常 / 超时,Broker 会:
问 Producer:你刚才那条本地事务到底成功还是失败?
Producer 的本地事务日志(或数据库)决定最终 commit/rollback。
⭐ 事务消息的保证能力
| 能力 | 是否保证 |
|---|---|
| 不丢消息 | ✔ Broker 落地 half message |
| 不重复 | ✘ 需要业务幂等 |
| 顺序 | ✘ 默认不保证 |
| 最终一致 | ✔ 强保证 |
⭐ 事务消息适用场景
- 下单 → 扣库存
- 扣余额 → 写流水
- 更改订单状态 → 推送事件
- 用户开通会员 → 发券 → 发放权益
这是你项目中最常见的链路。
⭐ 方式二:本地消息表(可靠消息服务化)
这是早年广泛使用的方案,核心思想:
业务落库时一起落“消息表”,由后台任务不断投递到 MQ,投递成功后标记成功。
流程:
-
业务端(订单系统)
- 开启事务
- 写订单表
- 写本地消息表(status=NEW)
- 事务提交
-
异步任务:轮训消息表 → 投递到 MQ
-
投递成功后更新消息表状态(success)
-
消费端幂等处理
⭐ 优点
- 所有数据在本地库,100% 不丢
- 成熟 + 可观测 + 可补偿
⭐ 缺点
- 表会增长,需要归档
- 系统扩展性一般
- 消息一致性依赖 DBA
现在很多公司把这个模式“服务化”,也叫:
可靠消息服务(RON)or 可靠事件服务
⭐ 方式三:Outbox 模式(DDD/微服务标准做法)
核心思想:
业务服务先把事件写入自己的 Outbox 表,由 CDC(Debezium)监听 Binlog 推送到 MQ。
步骤:
-
事务内:
- 写业务表
- 写 outbox 表(领域事件)
-
binlog 监听 outbox 表
-
Debezium → Kafka/RocketMQ
-
消费端处理事件
这种方式是现代微服务标准(尤其是在 Kafka 生态中盛行)。
RocketMQ 的 CDC 也越来越成熟。
⭐ 方式四:幂等 + 重试 + 补偿(最终一致性的必备基础)
无论用哪种方式,最终一致性少不了三件套:
① 消费幂等
- 唯一 key 去重(订单号、业务流水号)
- Redis SET/EX
- MySQL 唯一索引
- 状态机(状态 >= 当前状态时拒绝重复写)
② 重试机制
RocketMQ 有:
- retry topic(16 次)
- DLQ(死信队列)
- 人工补偿
③ 补偿机制(审计任务)
用来兜底:
- 定时任务扫未完成状态
- 比对两边状态
- 自动补发消息
🚀 三、RocketMQ 最终一致性的“完整落地方案”(面试直接给这套)
这是企业级最佳实践:
🔥 标准做法:事务消息 + 幂等消费 + 重试 + 补偿
1)生产端(Producer)
- 使用事务消息:sendMessageInTransaction
- Half Message 落地 → 不丢
- 本地事务成功 → commit
- 本地事务失败 → rollback
2)Broker 侧
- half message 落地
- 执行回查机制
- 异常情况下仍能问出事务领域结果
3)消费端
- 失败返回 RECONSUME_LATER
- 自动重试(16 次)
- 幂等处理
- 最终进入死信队列
4)补偿机制
- 每 5 分钟扫描业务状态
- 对不一致的订单补发事件 or 人工处理
🚀 四、面试官最喜欢问的:“最终一致性会不会出现脏数据?”
答案要抓住关键两点:
RocketMQ 做不到强一致,只能最终一致(异步);但只要幂等 + 重试 + 补偿设置到位,就不会出现不可修复的脏数据。
关键点回答:
- 消费者要幂等
- 不成功就一定重试
- 多次失败进入 DLQ
- 定时补偿扫业务状态
- 人工兜底审计
面试官会点头,因为这就是实际工程落地的方法。
🚀 五、面试最佳答案模板(你可以直接背)
RocketMQ 的最终一致性主要通过“事务消息 + 回查机制”来保障:
Producer 先发送 half message,Broker 落地但不投递;
然后执行本地事务,最终返回 commit 或 rollback。
如果 Producer 超时,Broker 会反向回查事务状态,确保最终一致。消费端则依赖幂等设计、重试机制和死信队列,失败的消息不会丢失。
另外还需配合业务侧的补偿任务对状态做审计修正。RocketMQ 不保证强一致,但能保证最终一致性,并且不会出现不可恢复的数据错误。
面试官听到这段会非常满意。