Broker回查事务消息
如果用户由于某种原因,在第二阶段中没有将endTransaction消息发送给Broker,Broker的Half消息又将如何处理。RocketMQ在设计时已经考虑到这个问题,通过"回查机制"处理第二阶段既未发送Commit
也没有发送Rollback的消息。回查是Broker发起的,Broker认为在接收Half消息后的一段时间内,如果生产者都没有发送Commit或Rollback消息给Broker,那么Broker会主动"询问"生产者该事务消息对应的本地事务执行结果,以此来决定事务是否要Commit.TransactionalMessageCheckService是回查服务的实现类
TransactionalMessageCheckService是一个线程服务,它在后台一直执行run()方法,run()方法一直调用waitForRunning()方法。关于waitForRunning()方法,这是RocketMQ的Broker中典型的"sleep"实现方式。该方式可以大致理解为"休息"一段时间再执行onWaitEnd()
方法,而TransactionalMessageCheckService服务重写了onWaitEnd()方法.
接下来分析下代码中的核心变量。
timeout
事务消息超时时间,如果消息在这个事件内没有进行Commit或Rollback,则执行第一次回查,默认6000ms
checkMax
最大回查次数,如果回查超过这个次数,事务消息将被忽略。回查的实现逻辑是每间隔一定时间执行TransactionalMessageServiceImpl#check()方法,判断哪些消息超时,对超时的消息开始执行回查
事务消息的最大回查次数默认15次
发送Half事务消息、执行Commit/Rollback命令、事务回查过程简图
- RMQ_SYS_TRANS_HALF_TOPIC:保存事务消息的Topic,它存储
用户发送的Half消息,有的消息已经进行了Rollback,有的消息
状态是未知的 - RMQ_SYS_TRANS_OP_HALF_TOPIC:也叫OP主题,当事务消息被Commit或Rollback后,会将原始事务消息的offset保存在该OP主题中
- RMQ_SYS_TRANS_HALF_TOPIC和RMQ_SYS_TRANS_OP_HALF_TOPIC配合流程。
首先,取出RMQ_SYS_TRANS_HALF_TOPIC中达到回查条件但没有回查过的消息,到RMQ_SYS_TRANS_OP_HALF_TOPIC主题中确认是否已经会回查,如果没有回查过则发起回查操作。
然后具体分析会回查方法TransactinalMessageServiceImpl.check()的实现过程。获取RMQ_SYS_TRANS_HALF_TOPIC主题的全部队列,依次循环每一个队列中的全部未消费的消息,确认是否需要回查。
对于每一条消息又是如何确认是否需要回查的呢?具体逻辑在TransactionalMessageServiceImpl#check()方法中的while(true)代码中
- 第一步:回查前校验。如果当前回查执行的时间超过了最大允许的回查时间,(默认为60s)则跳出当前回查过程,如果当前回查的消息已经执行了Commit/Rollback,则忽略当前消息,直接回查下一条消息
校验代码中的核心变量:
MAX_PROCESS_TIME_LIMIT:回查时间限制,默认是60s且不能配置
removeMap:该变量用于存储已经执行Commit/Rollback的Half消息位点
i:当前回查的Half消息的位点值。如果当前Half消息在回查时,即在允许的回查时间内,又没有被生产者进行Commit/Rollback
填充removeMap的过程 - 第二步:检查是否有消息需要回查。如果从RMQ_SYS_TRANS_HALF_TOPIC主题中获取Half消息为空的次数超过允许的最大次数或者没有消息,那么表示目前没有需要再回查的
消息了,可以结束本次回查过程,当然如果传入的位点是非法的,则继续下一个回查的位点。代码中的核心参数:
msgExt:Half消息对象
getMessageNullCount:当前空消息的次数
MAX_RETRY_COUNT_WHEN_HALF_NULL:表示可以允许的最大Half消息为空的次数,超过则结束回查,默认为1次,并且不能配置。
messageQueue:RMQ_SYS_TRANS_HALF_TOPIC主题中正在被检查的队列如果RMQ_SYS_TRANS_HALF_TOPIC中已经没有待回查的消息,则立即终止当前的回查过程
- 第三步,回查次数校验,消息是否过期校验。如果Half消息回查次数已经超过了允许的最大回查次数,则不再回查,实现该校验的方法是TransactionMessageServiceImpl.needDisard();如果Half消息对应的CommitLog已经过期,那么也不回查,该校验实现的方法是
TransactionalMessageServiceImpl.needSkip()
- 第四步:新发送的Half消息不用回查,对于不是新发送的Half消息,如果在免疫回查时间(免疫期)内,也不用回查。免疫期是指生产者在发送Half消息后、执行Commit/Rollback前,Half消息都不需要回查,这段时间就是这个Half消息的回查免疫期。免疫期的判断逻辑如图。代码中的核心变量如下:
valueOfCurrentMinusBorn:当前时间减去消息的发送时间
checkImmunityTimeStr:用户设置的消息回查免疫时间,换言之,就是生产者本地事务的最长执行时间,也就是默认6s.
当checkImmunityTimeStr和transactionTimeout同时存在时,免疫时间将通过getImmunityTime(checkImmunityTimeStr, transactionTimeout)方法计算后可以得出最终的免疫期,进而进行免疫期回查判断
- 第五步:最终判断是否需要回查生产者本地事务执行结果,满足图中条件之一就可以进行回查:
1.如果没有OP消息,并且当前Half消息在免疫期外
2.当前Half消息存在OP消息,并且最后一个本批次OP消息中的最后一个消息在免疫期外,也就是满足回查时间
3.Broker与客户端有时间差
4.重新将当前Half消息存储在RMQ_SYS_TRANS_HALF_TOPIC主题中,原因是回查是一个异步过程,Broker不确定回查的结果是成功还是失败,所以RocketMQ做最坏的打算,如果回查失败则下次继续回查;如果本地回查成功则写入OP消息,下次再读取Half消息时也不会回查
- 第六步:执行回查。在当前批次的Half消息回查完毕后,更新Half主题和OP主题的消费位点,推进回查进度。Broker将回查消息通过回查线程池异步地发送给生产者,执行事务结果回查