RocketMQ源码深入剖析
9 分布式事务消息源码分析
9.1 什么是分布式事务?
业务场景:用户A转账100元给用户B,这个业务比较简单,具体的步骤:
1、用户A的账户先扣除100元
2、再把用户B的账户加100元
如果在同一个数据库中进行,事务可以保证这两步操作,要么同时成功,要么同时不成功。这样就保证了转账的数据一致性。
但是在微服务架构中,因为各个服务都是独立的模块,都是远程调用,都没法在同一个事务中,都会遇到分布式事务问题。
9.2 RocketMQ的解决方案
RocketMQ采用两阶段提交,把扣款业务和加钱业务异步化,在A系统扣款成功后,发送“扣款成功消息”到消息中间件;B系统中加钱业务订阅“扣款成功消息”,再对用户进行加钱。
9.2.1 解决方案的问题
在哪个阶段向RocketMQ发送消息?
1、先扣款后再向RocketMQ发消息
先扣款再发送消息,万一发送消息超时了(MQ中有可能成功,有可能失败),那这个状态就很难判断了
2、先向RocketMQ发消息后再扣款
扣款成功消息发送成功,但是如果本地扣款业务失败了,那消息已经发给MQ了,第二阶段的加钱就会执行成功。
所以我们发现,无论是哪种方案,处理起来都会有问题。
其实仔细分析下,问题的关键点,就是RocketMQ改变不了消息发送者的事务状态。所以RocketMQ的分布式事务方案进行了优化。
9.3 RocketMQ的分布式事务方案
所以RocketMQ在分布式事务中引入了半事务及事务回查机制。
半事务:
发一个消息到rocketmq,但该消息只储存在commitlog中,但consumeQueue中不可见,也就是消费端(订阅端)无法看到此消息。
事务回查:
RocketMq会定时遍历commitlog中的半事务消息,这个事务回查机制就可以站在 RocketMQ的角度参与消息发送者的事务中。
9.4 RocketMQ的分布式事务案例代码
这个是分布式事务的生产者,完成了半事务的发送。
通过事务回查,如果在TransactionListenerImpl类executeLocalTransaction方法中,如果本地事务执行成功,则提交commit_message,消费端即可消费消息
如果有一些比较耗时的操作导致,不能在这个步骤确认的话,可以提交UNKNOW,交给定时的任务回查来处理
另外一点,如果担心生产者发生故障导致分布式事务的问题话,定时事务回查是可以在生产者群组中做的。
我们可以做一个这样的案例,一个生产者1,一个生产者2,消费发送时的分组是一样,都使用分布式事务消息。
生产者1发生故障了,消息状态还是一个UNKNOW状态,只要生产者2还存活,生产者2就可以帮助生产者1完成事务回查的确认,从而不会有单点故障问题。
9.5 分布式事务源码分析
从分布式事务的流程上,我们分析源码,可以从消息发送,确认/回滚 ,回查三个方面。
9.5.1 消息发送源码分析
Producer
Broker
RocketMQ使用Netty处理网络,broker收到消息写入的请求就会进入SendMessageProcessor类中processRequest方法。
最终进入DefaultMessageStore类中asyncPutMessage方法进行消息的存储
结合图同时结合代码,我们可以看到,在事务消息发送时,消息实际存储的主题是一个系统主题:RMQ_SYS_TRANS_HALF_TOPIC
同时消息中保存着消息的原有主题相关的信息与队列
9.5.2 确认/回滚源码分析
Producer
DefaultMQProducerImpl类sendMessageInTransaction方法
Broker
EndTransactionProcessor类
9.5.3 回查源码分析
Producer
事务回查中,Producer是服务端,所以需要注册服务处理
DefaultMQProducerImpl类checkTransactionState方法
DefaultMQProducerImpl类processTransactionState方法
Broker
在Broker启动的时候,是要作为客户端,定期的访问客户端做事务回查。
回顾到之前讲到《6.1 Broker启动流程分析》
事务回查是Broker发起的一次定时的网络调用(每隔60s),所以事务回查在客户端启动的时候第一次不一定是60s的间隔,一般会小于60s(因为事务回查是broker发起的,并不是client端定时发起)