本地消息表
核心思路是将分布式事务拆分成本地事务进行处理。
在库存服务和订单服务之间引入mq后,此时又引入了新的问题。大概列举如下:
- 若库存服务完成支付逻辑后,在投递消息到mq中间件的过程中由于网络抖动等原因,没有投递到mq中导致消息丢失了怎么办?
- mq接收到消息后,由于内部原因导致消息丢失了怎么办?
- 订单服务在监听消息的过程中,由于网络原因没有接收到消息或消费过程中遇到异常,此时也会导致消息丢失,测试怎么办?
经过以上分析,mq可能会丢失消息,传统的mq没有实现分布式事务,因此这里可以引入本地消息表结合mq的方式来解决分布式事务的问题,保证消息的可靠投递。
正向流程步骤大概如下:
- 在库存系统中引入一张消息表来记录消息,即库存系统扣减库存操作成功后同时往消息表插入一条扣减库存操作的消息,状态为“代确认”。注意扣减库存逻辑和插入消息表的代码要包裹在一个事务里面,这里保证了本地事务的强一致性。
- 向mq队列中投递一条消息,这条消息的内容跟保存在库存系统消息表的消息内容一致。
- mq接收到消息后,此时订单服务也监听到这条消息了,此时订单服务处理消费逻辑即开始生成订单。
- 订单服务生成订单后,反向向mq投递一条消费成功的消息到队列中
- 同时库存系统又来监听这个订单系统消费成功的消息,当库存服务监听到这个消费成功的消息后,此时再将本地消息表的消息状态改为“已发送”。
- 当步骤 1 处理出错,事务回滚,相当于什么都没发生。
- 如果是业务上的失败,事务被动方可以发消息给事务主动方进行回滚。
- 如果多个事务被动方已经消费消息,事务主动方需要回滚事务时需要通知事务被动方回滚。
通过正向流程可以得出,只要第一步操作成功就可以通过消息重复投递的方式来达到最终一致性,不过消费者的消费接口要实现幂等性。
方案总结
方案的优点如下:
- 从应用设计开发的角度实现了消息数据的可靠性,消息数据的可靠性不依赖于消息中间件,弱化了对 MQ 中间件特性的依赖。
- 方案轻量,容易实现。
缺点如下:
- 与具体的业务场景绑定,耦合性强,不可公用。
- 消息数据与业务数据同库,占用业务系统资源。
- 业务系统在使用关系型数据库的情况下,消息服务性能会受到关系型数据库并发性能的局限。
可靠消息最终一致性方案
方案简介
基于 MQ 的分布式事务方案其实是对本地消息表的封装,将本地消息表基于 MQ 内部,其他方面的协议基本与本地消息表一致。
处理流程
下面主要基于 RocketMQ 4.3 之后的版本介绍 MQ 的分布式事务方案。
在本地消息表方案中,保证事务主动方发写业务表数据和写消息表数据的一致性是基于数据库事务,RocketMQ 的事务消息相对于普通 MQ,相对于提供了 2PC 的提交接口,方案如下:
正向流程步骤大概如下:
- 发送方向 MQ 服务端(MQ Server)发送 half 消息。
- MQ Broker 将消息持久化成功之后,向发送方 ack 确认消息已经发送成功。
- 发送方开始执行本地事务逻辑。
- 发送方根据本地事务执行结果向 MQ Broker 提交二次确认(commit 或是 rollback)。
- MQ Broker 收到 commit 状态则将半消息标记为可投递,订阅方最终将收到该消息;MQ Broker 收到 rollback 状态则删除半消息,订阅方将不会接受该消息。
异常流程步骤大概如下:
在断网或者应用重启等异常情况下,图中 4 提交的二次确认超时未到达 MQ Server,此时处理逻辑如下:
- 图中 5:MQ Server 对该消息发起消息回查。
- 图中 6:发送方收到消息回查后,需要检查对应消息的本地事务执行的最终结果。
- 图中 7:发送方根据检查得到的本地事务的最终状态再次提交二次确认。
- 图中 8:MQ Server基于 commit/rollback 对消息进行投递或者删除。
方案总结
相比本地消息表方案,MQ 事务方案优点是:
- 消息数据独立存储 ,降低业务系统与消息系统之间的耦合。
缺点是:
- 一次消息发送需要两次网络请求(half 消息 + commit/rollback 消息) 。
- 业务处理服务需要实现消息状态回查接口。