本实例基于 冰河《深入理解分布式事务原理与实战》中第16章案例实现。
- 场景说明:
通过模拟商城业务中的下单扣减库存的场景:
提交订单时,订单服务向RocketMQ发送事务消息,RocketMQ成功接收到消息后,会向订单服务返回确认消息。此时,订单服务执行本地事务,将订单相关信息落库。若订单服务本地事务执行成功,则会向RocketMQ发送提交事务的消息,否则,则会向RocketMQ发送事务回滚的消息。
库存服务订阅RocketMQ的消息,如果收到订单确认成功的消息后,就会执行本地事务,扣减库存。库存扣减异常,可以通过MQ消息,通知订单服务,执行回滚操作。
整体流程如下所示:
第一步:订单微服务向RocketMQ发送Half消息。
第二步:RocketMQ向订单微服务响应Half消息发送成功。
第三步:订单微服务执行本地事务,向本地数据库中插入、更新、删除数据。
第四步:订单微服务向RocketMQ发送提交事务或者回滚事务的消息。
第五步:如果库存微服务未收到消息,或者执行事务失败,且RocketMQ未删除保存的消息数据,RocketMQ会回查订单微服务的接口,查询事务状态,以此确认是再次提交事务还是回滚事务。
第六步:订单微服务查询本地数据库,确认事务是否执行成功。
第七步:订单微服务根据查询出的事务状态,向RocketMQ发送提交事务或者回滚事务的消息。
第八步:如果第七步中订单微服务向RocketMQ发送的是提交事务的消息,则RocketMQ会向库存微服务投递消息。
第九步:如果第七步中订单微服务向RocketMQ发送的是回滚事务的消息,则RocketMQ不会向库存微服务投递消息,并且会删除内部存储的消息数据。
第十步:如果RocketMQ向库存微服务投递的是执行本地事务的消息,则库存微服务会执行本地事务,向本地数据库中插人、更新、删除数据。
第十一步:如果RocketMQ向库存微服务投递的是查询本地事务状态的消息,则库存微服务会查询本地数据库中事务的执行状态。
环境搭建:
- 搭建RocketMQ环境:
参考:
https://blog.csdn.net/weixin_46475802/article/details/128541293
- 搭建JAVA环境:
-
创建数据库:
DROP DATABASE IF EXISTS `tx-msg-order`; CREATE DATABASE `tx-msg-order` charset utf8; DROP DATABASE IF EXISTS `tx-msg-stock`; CREATE DATABASE `tx-msg-stock` charset utf8;
-
拉取代码:
地址:https://gitee.com/Romen_Geng/tx-msg.git
-
启动环境:
数据库:
-
Postman测试:
服务日志:
-
模拟订单异常:
// 模拟订单异常 if (Math.random() > 0) { throw new RuntimeException("模拟订单异常"); }
订单本地事务执行失败,MQ消息对库存服务不可见。- 模拟库存异常:
-
// 模拟库存异常
if (Math.random() > 0) {
throw new RuntimeException("模拟库存异常");
}
订单需要进行自定义业务逻辑处理,否则会一直回查本地事务。
自定义订单回滚逻辑: