假设消息中间件没有提供“事务消息”功能,好比你用的是Kafka。那如何解决分布式事务呢?网络
解决方案以下:
(1)Producer端准备1张消息表,把update DB和insert message这2个操做,放在一个DB事务里面。分布式
(2)准备一个后台程序,源源不断的把消息表中的message传送给消息中间件。失败了,不断重试重传。容许消息重复,但消息不会丢,顺序也不会打乱。源码分析
(3)Consumer端准备一个判重表。处理过的消息,记在判重表里面。实现业务的幂等。但这里又涉及一个原子性问题:若是保证消息消费 + insert message到判重表这2个操做的原子性?this
消费成功,但insert判重表失败,怎么办?关于这个,在Kafka的源码分析系列,第1篇, exactly once问题的时候,有过讨论。spa
经过上面3步,咱们基本就解决了这里update db和发送网络消息这2个操做的原子性问题。设计
但这个方案的一个缺点就是:须要设计DB消息表,同时还须要一个后台任务,不断扫描本地消息。致使消息的处理和业务逻辑耦合额外增长业务方的负担。code
为了能解决该问题,同时又不和业务耦合,RocketMQ/Notify提出了“事务消息”的概念。中间件
具体来讲,就是把消息的发送分红了2个阶段:Prepare阶段和确认阶段。队列
具体来讲,上面的2个步骤,被分解成3个步骤:
(1) 发送Prepared消息
(2) update DB
(3) 根据update DB结果成功或失败,Confirm或者取消Prepared消息。事务
可能有人会问了,前2步执行成功了,最后1步失败了怎么办?这里就涉及到了RocketMQ的关键点:RocketMQ会按期(默认是1分钟)扫描全部的Prepared消息,询问发送方,究竟是要确认这条消息发出去?仍是取消此条消息?
具体代码实现以下:
也就是定义了一个checkListener,RocketMQ会回调此Listener,从而实现上面所说的方案。
// 也就是上文所说的,当RocketMQ发现`Prepared消息`时,会根据这个Listener实现的策略来决断事务 TransactionCheckListener transactionCheckListener = new TransactionCheckListenerImpl(); // 构造事务消息的生产者 TransactionMQProducer producer = new TransactionMQProducer("groupName"); // 设置事务决断处理类 producer.setTransactionCheckListener(transactionCheckListener); // 本地事务的处理逻辑,至关于示例中检查Bob帐户并扣钱的逻辑 TransactionExecuterImpl tranExecuter = new TransactionExecuterImpl(); producer.start() // 构造MSG,省略构造参数 Message msg = new Message(......); // 发送消息 SendResult sendResult = producer.sendMessageInTransaction(msg, tranExecuter, null); producer.shutdown();
public TransactionSendResult sendMessageInTransaction(.....) { // 逻辑代码,非实际代码 // 1.发送消息 sendResult = this.send(msg); // sendResult.getSendStatus() == SEND_OK // 2.若是消息发送成功,处理与消息关联的本地事务单元 LocalTransactionState localTransactionState = tranExecuter.executeLocalTransactionBranch(msg, arg); // 3.结束事务 this.endTransaction(sendResult, localTransactionState, localException); }
总结:对比方案2和方案1,RocketMQ最大的改变,其实就是把“扫描消息表”这个事情,不让业务方作,而是消息中间件帮着作了。
至于消息表,其实仍是没有省掉。由于消息中间件要询问发送方,事物是否执行成功,仍是须要一个“变相的本地消息表”,记录事物执行状态。
可能有人又要说了,不管方案1,仍是方案2,发送端把消息成功放入了队列,但消费端消费失败怎么办?
消费失败了,重试,还一直失败怎么办?是否是要自动回滚整个流程?
答案是人工介入。从工程实践角度讲,这种整个流程自动回滚的代价是很是巨大的,不但实现复杂,还会引入新的问题。好比自动回滚失败,又怎么处理?
对应这种极低几率的case,采起人工处理,会比实现一个高复杂的自动化回滚系统,更加可靠,也更加简单。