分布式事务-RocketMQ事务消息源码分析

前言

上篇文章介绍了RocketMQ事务消息的基本原理,对大致的流程及设计思路有个大致的了解

分布式事务-RocketMQ消息事务设计思路及Demo

文章最后,也提出了几个问题

  1. 为什么prepare消息在发送后不会被消费?
  2. 事务消息又是如何提交、回滚的?
  3. 定时回查本地事务状态的机制又是怎么样?

废话不多说,直接盘源码,从源码中找问题答案,源码中都加了todo注释,方便大家查看

发送流程

先从事务消息发送方法入手TransactionMQProducer#sendMessageInTransaction
在这里插入图片描述
进到DefaultMQProducerImpl#sendMessageInTransaction方法
在这里插入图片描述
这是一个典型的两阶段提交过程 步骤1、2为第一阶段,步骤3为第二阶段
1、producer发送prepare消息给mq服务器
2、如果消息发送成功,执行本地事务,同时将消息transactionId与业务操作一并入库,方便后续事务回查
3、根据本地事务执行状态,决定是否对prepare消息进行提交/回滚,本地事务执行状态有下面几种
在这里插入图片描述

这时候再反过来看事务消息原理设计图,是不是清晰很多
在这里插入图片描述
方法中还剩两个关键方法

  1. this.send(msg)
  2. this.endTransaction(sendResult, localTransactionState, localException)

下面逐个分析,先看消息发送send()方法

事务消息发送

事务消息的发送,会执行到DefaultMQProducerImpl#sendDefaultImpl方法,再调用DefaultMQProducerImpl#sendKernelImpl方法发送消息,下面只贴出与事务消息相关代码
在这里插入图片描述
TRANSACTION_PREPARED_TYPE类型即半消息类型(prepare消息)

在消息发送之前,进到SendMessageProcessor#sendMessage方法,包含如下代码
在这里插入图片描述

事务消息与普通消息发送基本没区别,只是给消息属性PROPERTY_TRANSACTION_PREPARED打了个标记true,后续事务存储也是根据该标记进行特殊处理

事务消息存储

上面代码已经看到,TransactionalMessageService#prepareMessage方法是对事务消息进行存储的方法,看下代码
在这里插入图片描述
会调用TransactionalMessageBridge#putHalfMessage进行消息存储
在这里插入图片描述
关键点在于parseHalfMessageInner方法
在这里插入图片描述
注释里写的很清楚,核心就是两点

  1. 修改消息topic为RMQ_SYS_TRANS_HALF_TOPIC,并备份消息原有topic,供后续commit消息时还原消息topic使用
  2. 修改消息queueId为0,并备份消息原有queueId,供后续commit消息时还原消息queueId使用

看到这,也就回答了第一个问题 为什么prepare消息在发送后不会被消费?

修改完topic和queueId后,事务消息也会像普通消息一样存储在commitLog中

消息事务提交/回滚

RMQ_SYS_TRANS_HALF_TOPIC对应队列,后面简称halfQueue
RMQ_SYS_TRANS_OP_HALF_TOPIC对应队列,后面简称opQueue

回到发送流程中提到的this.endTransaction方法,在本地事务执行后,会根据LocalTransactionState来向mq服务器发送不同的请求,来看下具体代码
在这里插入图片描述

对事务结束请求EndTransactionRequestHeader的处理方法在EndTransactionProcessor#processRequest方法中,看下关键逻辑
在这里插入图片描述

提交

提交消息的分为四个步骤

  1. 根据commitLog查询对应事务消息,对应TransactionalMessageService#commitMessage方法
  2. 从消息属性 PROPERTY_REAL_TOPIC 及 PROPERTY_REAL_QUEUE_ID 中,取出并恢复消息原来的 topic,queueId,对应endMessageTransaction方法
    看下代码
    在这里插入图片描述
  3. 调用 MessageStore#putMessage 将还原后的消息存储到commitLog中,对应sendFinalMessage方法
  4. 删除消息(将消息移到topic为RMQ_SYS_TRANS_OP_HALF_TOPIC队列中),对应TransactionalMessageService#deletePrepareMessage方法
    看下实现
    在这里插入图片描述

最终调用了TransactionalMessageBridge#addRemoveTagInTransactionOp方法
在这里插入图片描述
核心点在于

  1. 初始化了一个Top为RMQ_SYS_TRANS_OP_HALF_TOPIC的消息
  2. 消息体为半消息队列的offSet,方便索引到半消息队列中的消息,这里是回查机制设计的核心,下面会提到
  3. halfQueue中处理过的消息,其offSet才会存在于opQueue的消息体中,后续消息回查时会用到

回滚

直接对应提交消息中的第四步,调用TransactionalMessageService#deletePrepareMessage方法,将消息put到topic为RMQ_SYS_TRANS_OP_HALF_TOPIC的队列中

看到这,对消息提交、回滚做了说明,无论消息是提交还是回滚,消息都会被put到topic为RMQ_SYS_TRANS_OP_HALF_TOPIC的队列中,总结下两个Topic的作用

RMQ_SYS_TRANS_HALF_TOPIC:prepare消息的主题,事务消息首先先进入到该主题。
RMQ_SYS_TRANS_OP_HALF_TOPIC:当消息服务器收到事务消息的提交或回滚请求后,会将消息存储在该主题下

开头的第二个问题 事务消息又是如何提交、回滚的? 也迎刃而解

那么还有第三个问题 定时回查本地事务状态的机制又是怎么样?

事务消息回查

事务消息回查的实现在TransactionalMessageCheckService中,代码如下
在这里插入图片描述
详细分析下TransactionalMessageService#check方法

在这里插入图片描述

再来看下for循环中处理逻辑,由于代码较长,分段截取并注释说明
1、先从halfQueue,opQueue中取出对应offSet
在这里插入图片描述
2、根据halfQueue,opQueue判断出opQueue中哪些消息已经处理过,哪些没处理过

处理过的opQueue offSet放入doneOpOffset中
在这里插入图片描述

具体实现逻辑如下,核心就是拿opQueue中消息体内的halfQueue的offSet(已经处理过的半消息offSet)与当前halfQueue的offSet做比较
在这里插入图片描述

3、接下来就到了消息回查的实现关键,先看下整体流程
在这里插入图片描述

核心回查在else中

4、消息拉取逻辑,判断回查次数、存储时间逻辑如下
在这里插入图片描述

5、判断是否该条消息存储时间,是否超过了立即执行回查的超时时间checkImmunityTime,没超时则不执行回查,逻辑如下
在这里插入图片描述
6、重置消息消费位点queueOffSet,putBackHalfMsgQueue在这里插入图片描述
首先判断是否需要进行消息事务状态回查,isNeedCheck

valueOfCurrentMinusBorn > checkImmunityTime,这个逻辑很好理解,当前消息存储时间已经超过了超时时间,应当进行事务回查

还有个逻辑是判断当前获取的最后一条OpMsg的存储时间是否超过了事务超时时间,如果为true也要进行事务状态回查,为什么要这么做呢?

因为在下文中,如果isNeedCheck=true,会调用putBackHalfMsgQueue重新将opMsg放入opQueue中,重新放入的消息被重置了queueOffSet,commitLogOffSet,即将消费位点前移了,放到opQueue最新一条消息中

所以

如果事务状态回查成功,则fillOpRemoveMap会使得doneOpOffset包含该halfQueue offSet,即使消费位点前移了,后续也不会再重复处理

如果事务状态回查失败,则判断拉取到的32条消息的最新一条消息存储时间是否超过超时时间,如果是,那肯定是回查失败的,继续进行回查
在这里插入图片描述

7、具体事务回查代码逻辑AbstractTransactionalMessageCheckListener#resolveHalfMsg中,启动了一个线程池来进行事务回查
在这里插入图片描述
8、再看下sendCheckMessage方法
在这里插入图片描述

9、接收事务回查请求逻辑在ClientRemotingProcessor#checkTransactionState中,各种请求的接受者的查询逻辑可以根据RequestCode中的变量值来找,看下具体代码
在这里插入图片描述
至于checkTransactionState,与事务提交部分代码较为相似,只不过这里是调用TransactionListener#checkLocalTransaction来得到事务执行状态码localTransactionState,再根据localTransactionState来发送commit/rollback请求,不再赘述

至此,第三个问题 定时回查本地事务状态的机制又是怎么样也解决了

发布了43 篇原创文章 · 获赞 134 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/hosaos/article/details/90240260