分布式事务:消息最终一致性
一、什么是可靠消息最终一致性事务
可靠消息最终一致性方案是指当事务发起方执行完成本地事务后并发出一条消息,消息消费者一定能够接收消息并处理事务成功,此方案强调的是只要消息发给事务参与方最终事务要达成一致。
涉及的思想是:把一系列事务拆分成本地事务。
基本原理:
- 事务发起者A 执行本地事务
- 事务发起者 A通过 MQ 将需要执行的事务信息发送给事务参与者 B
- 事务参与者 B 接收到消息后执行本地事务
此方案是基于消息中间件来实现的,具体流程如下图所示:
事务发起方将消息发给消息中间件,事务参与方从消息中间件接收消息,事务发起方和消息中间件之间,事务参与方(消息消费方)和消息中间件之间都是通过网络,由于网络通信的不确定性会导致分布式事务问题。
因此,可靠消息最终一致性方案要解决以下几个问题:
-
1、本地事务与消息发送的原子性问题
- 也就是事务发起方执行本地事务和发送消息这两件事情 ,要么全部成功,要么全部是失败,这必须是原子性操作。
- 如果先执行本地事务,发送消息失败了怎么办?如果先发送消息,执行本地事务失败了怎么办?如何保证它们原子性呢?
-
2、事务参与方接收消息的可靠性
- 事务参与方必须能够从消息队列接收到消息,如果接收到消息失败可以重复接收消息
-
3、消息重复消费的问题
- 由于网络2 的存在,若某个消费节点超时但是消费成功,此时消息中间件会重复投递此消息,就导致消息的重复消费。
- 因此,消息消费需要保证幂等性。
二、解决方案
针对上述的一系列问题,如何解决它们呢,这里提供两种解决方案。
2.1 本地消息表
本地消息表是由 eBay 提出的,此方案的核心是将分布式事务拆分成本地事务进行处理,然后通过定时任务将消息发送至消息中间件,待确认消息发送给消费方成功再将消息删除。
拿下订单扣减库存为例:
- 步骤 1 和2 执行本地事务,创建订单,然后发送消息到mq,更新订单和发送消息属于同一个事务。
- 步骤 34567 为事务参与方进行执行它的本地事务,如果执行完成则删除消息。
- 步骤 8 则定时扫描本地消息表,看是否有未完成的任务,有则重试,防止消费者消费失败。
优点:
- 从应用设计开发的角度实现了消息数据的可靠性,消息数据的可靠性不依赖于消息中间件,弱化了对 MQ 中间件特性的依赖。
缺点:
- 与具体的业务场景绑定,耦合性强,不可公用。消息数据与业务数据同库,占用业务系统资源。业务系统在使用关系型数据库的情况下,消息服务性能会受到关系型数据库并发性能的局限。
- 需要维护定时任务
2.2 RocketMQ 事务消息方案
1、事务发起方发送prepare消息到MQ
2、消息发送成功后执行本地事务
3、根据本地事务执行结果返回commit或者是rollback
4、如果消息时rollback,MQ将删除该prepare消息不进行下发,如果是commit消息,mq将会把这个消息发送到consumer
5、执行本地事务的过程中,执行端挂掉,或者超时,MQ将会不停的询问其同组的其他producer来获取状态
6、Consumer端的消费成功机制有MQ保证
RocketMQ 消息中间把消息分为两个阶段:Prepared 阶段和 确认阶段。
- Prepared 阶段:
该阶段主要发一个消息到 rocketmq,但该消息只存储在 commitlog 中,但 消息队列 中不可见,也就是消费端(订阅端)无法看到此消息
- commit /rollback 阶段(确认阶段)
该阶段主要把 prepared 消息保存到 消息队列中,即让消费端可以看到此消息,也就是可以消费此消息。
仍然以下订单扣减库存为例:
其中需要说明的一点就是 RocketMQ它会定期回查状态,即遍历 commitlog 中的预备消息,去会查本地事务
的执行状态,发现本地事务没有执行成功就 rollback,如果成功就发送 commit 消息。此时回查状态可以设计
一张 事务表,把业务表和事务绑定在同一个本地事务中,如果本地事务执行完成,就将状态表的id 状态改为
true,到时回查该表即可。
优点:
-
消息数据独立存储,降低业务系统与消息系统之间的耦合。
-
吞吐量优于本地消息表方案。
缺点:
-
一次消息发送需要两次网络请求(half消息 + commit/rollback)。
-
需要实现消息回查接口。
参考
[转自:分布式事务:消息最终一致性]