一个操作需要跨多个服务调用才能完成,那么spring本地事务是不起作用的
为什么spring本地事务不起作用,看下面例子
public String createOrder() {
orderService.createOrder(); // 创建订单
itemService.decrCount(); // 减库存
userAccountService.updateAccount(); //用户账户更新
}
假设上面三个操作都需要调用不同的服务,显然,当第三个操作用户账户更新失败时,前两个操作并不会回滚,因为并不是一个spring域
因此就需要找到一种方案解决跨服务的事务问题
mq-transaction 父工程
mq-transaction-common 公共工程
mq-transaction-order 订单
mq-transaction-item-api
mq-transaction-item 商品
mq-transaction-message-api
mq-transaction-message 消息服务
mq_item 商品表
mq_item_user_record 商品购买记录表
mq_message 可靠消息服务表
mq_order 订单表
mq_user 用户表
两个服务
- 订单服务
- 商品购买记录服务
用户下单场景:
- 用户下单调用com.order.service.impl.OrderServiceImpl.createOrder()方法
- 首先生成订单,调用本地代码写入order表数据
- 调用mq-transaction-item商品服务com.item.service.impl.ItemServiceImpl.recordOrderItem() ,创建商品购买记录
- 一些必要的操作
- 完成下单
异常场景:
- 如果步骤2出错,那么spring本地事务产生作用,数据回滚,没问题
- 如果步骤3出错,首先商品服务的事务发挥作用,商品服务数据回滚;然后错误抛到 调用者订单服务里,订单服务本地事务作用,订单服务数据回滚,数据都回滚,也没有问题
- 如果步骤4出错,本地代码在spring事务作用下数据会回滚,但是调用的商品服务 因为已经调用完成,不属于本地spring事务管理,所以数据不会回滚,出现问题了
如果步骤4出错,那么系统就会出现数据不统一情况,这是分布式系统不可避免的事务问题
针对出现场景不同,解决方法也不一样,这里只探讨可靠消息最终一致性方案
- 本地消息服务
- 独立消息服务
消息存储与发送都在本服务完成,也就是订单创建完成后,将需要投递的消息先存储 到数据库,然后再发送到消息队列。这里并没有调用item服务,先创建订单再保存并发送 消息到ActiveMQ,整个操作在本地spring事务中完成,保证了数据一致性。之后消息消费者 item服务从队列中取出消息消费,并修改消息状态。对于消费异常情况,需要有消息重发机制, 对异常消息进行重发。
- 本地消息服务异常情况分析
- 本地消息图服务流程图
-
步骤1、步骤2、步骤3如果出现异常,在spring本地事务作用下,数据会回滚, 消息不会保存,也不会发送到mq,能够保证数据一致性
-
步骤5、步骤6、步骤7出现异常,因为在订单服务中已经创建完订单,订单服务 数据是不可能在回滚了,那么要保证数据最终一致,就需要对消息进行重新投递, 对消费异常的消息重新入队,让消费者重新消费
-
在系统工作正常的情况下消息最终是会投递并成功消费的,这样,整个系统就 能保证最终数据的一致
- 异常消息重新发送实现
-
消息持久化到数据库后,如果正常消费完成的消息,就会在数据库中标记为消息成功
-
如果消费过程异常,那么该条消息在数据库中状态还是发送中,就需要取出该消息并重新发送
-
关键在于消息重新发送规则制定