使用mq如何保证分布式事务的最终一致性
大家好,我是程序员老张。今天聊聊分布式系统中高频面试题——如何用MQ保证分布式事务的最终一致性。面试官最爱问这个,因为实际业务中跨服务调用太常见了。先放个福利:2025年Java面试宝典
🔗 点击下载 提取码:9b3g
分布式事务的挑战
想象一个场景:用户下单后要扣库存和发优惠券。库存服务A和优惠券服务B是独立的,如果用同步调用:
- 调A扣库存成功
- 调B发券失败
- 此时库存已扣,数据不一致了!
这就是经典的分布式事务问题。最终一致性的核心思想是:允许短暂不一致,但最终必须一致。而MQ正是实现它的利器!
基于MQ的最终一致性方案
核心流程四步走:
1. 本地事务 + 消息生产
// 伪代码示例
transaction.begin(); // 开启本地事务
try {
orderService.createOrder(); // 写订单表
mq.send("stock_topic", "扣减库存"); // 发MQ(消息暂存本地)
transaction.commit(); // 提交事务(订单和消息同时持久化)
} catch (Exception e) {
transaction.rollback(); // 回滚
}
关键点:
- 消息必须和业务数据同库同事务
- 若事务提交失败,消息不会发出(避免脏数据)
2. 消息可靠投递
事务提交后,异步任务扫描本地消息表,将状态为"未发送"的消息投递到MQ:

图中步骤:
- 业务数据+消息存入DB
- 定时任务捞取未发送消息
- 投递到MQ
3. 消息可靠消费
消费者必须实现幂等性:
// 伪代码:用Redis防重
String msgId = message.getId();
if (!redis.setnx(msgId, "1", 24h)) {
return; // 已处理过
}
stockService.deduct(); // 执行真实逻辑
为什么幂等?
网络抖动可能导致MQ重复投递,不幂等会重复扣库存!
4. 最终一致性兜底
定时对账任务是最后防线:
-- 扫描订单成功但库存未扣的记录
SELECT * FROM orders
WHERE status='paid'
AND NOT EXISTS (SELECT 1 FROM stock_logs WHERE order_id=orders.id);
发现不一致则人工或自动修复。
异常处理与优化点
-
生产者投递失败:
- 本地消息表状态标记为"发送失败"
- 定时任务重试(需设最大重试次数)
-
消费者处理失败:
- 捕获异常后回滚业务并返回
RECONSUME_LATER - MQ会重新投递(RocketMQ默认16次)
- 捕获异常后回滚业务并返回
-
性能优化:
- 消息表按业务分库分表
- 批量发送消息(如RocketMQ的BatchProducer)
面试避坑指南
被问到这题时,务必强调以下关键词:
✅ 本地消息表 ✅ 事务性发件箱
✅ 消费端幂等 ✅ 定时对账补偿
避免只说"用MQ",会被追问细节!
最后分享个干货:如果你需要面试鸭会员,通过 面试鸭返利网 找我可返利25元!

(扫码或搜 面试鸭返利网 直达)
技术交流欢迎来面试鸭返利网找我,专注程序员副业变现和面试干货!
总结:MQ实现最终一致性的本质是将分布式事务拆解为多个本地事务,通过异步消息+重试+对账保证数据最终一致。实际用RocketMQ/Kafka时,记得开启事务消息功能(如RocketMQ TransactionMQProducer)简化流程!


