RocketMQ源码分析之RocketMQ事务消息实现原理中篇----事务消息状态回查

本文深入探讨RocketMQ的事务消息状态回查机制,分析TransactionalMessageCheckService线程如何定时检查RMQ_SYS_TRANS_HALF_TOPIC主题中的消息,了解事务消息如何在超时或达到最大检测次数后被提交或回滚。文章详细阐述了事务状态回查的实现逻辑,包括检查频率、消息队列处理、事务超时判断和回查消息的发送过程。
摘要由CSDN通过智能技术生成

在阅读本文前,若您对RocketMQ技术感兴趣,请加入 RocketMQ技术交流群

上节已经梳理了RocketMQ发送事务消息的流程(基于二阶段提交),本节将继续深入学习事务状态消息回查,我们知道,第一次提交到消息服务器时消息的主题被替换为RMQ_SYS_TRANS_HALF_TOPIC,本地事务执行完后如果返回本地事务状态为UN_KNOW时,第二次提交到服务器时将不会做任何操作,也就是说此时消息还存在与RMQ_SYS_TRANS_HALF_TOPIC主题中,并不能被消息消费者消费,那这些消息最终如何被提交或回滚呢?

原来RocketMQ使用TransactionalMessageCheckService线程定时去检测
RMQ_SYS_TRANS_HALF_TOPIC主题中的消息,回查消息的事务状态。TransactionalMessageCheckService的检测频率默认1分钟,可通过在broker.conf文件中设置transactionCheckInterval的值来改变默认值,单位为毫秒。

接下来将深入分析该线程的实现原理,从而解开事务消息回查机制。

TransactionalMessageCheckService#onWaitEnd
protected void onWaitEnd() {
        long timeout = brokerController.getBrokerConfig().getTransactionTimeOut();         // @1
        int checkMax = brokerController.getBrokerConfig().getTransactionCheckMax();    // @2
        long begin = System.currentTimeMillis();
        log.info("Begin to check prepare message, begin time:{}", begin);
        this.brokerController.getTransactionalMessageService().check(timeout, checkMax, this.brokerController.getTransactionalMessageCheckListener());       // @3
        log.info("End to check prepare message, consumed time:{}", System.currentTimeMillis() - begin);
    }

代码@1:从broker配置文件中获取transactionTimeOut参数值。
代码@2:从broker配置文件中获取transactionCheckMax参数值,表示事务的最大检测次数,如果超过检测次数,消息会默认为丢弃,即回滚消息。

接下来重点分析TransactionalMessageService#check的实现逻辑:

org.apache.rocketmq.broker.transaction.queue.TransactionalMessageServiceImpl
TransactionalMessageServiceImpl#check
String topic = MixAll.RMQ_SYS_TRANS_HALF_TOPIC;
Set<MessageQueue> msgQueues = transactionalMessageBridge.fetchMessageQueues(topic);
if (msgQueues == null || msgQueues.size() == 0) {
      log.warn("The queue of topic is empty :" + topic);
      return;
}

step1:根据主题名称,获取该主题下所有的消息队列。

TransactionalMessageServiceImpl#check
for (MessageQueue messageQueue : msgQueues) {
    // ...
}

Step2:循环遍历消息队列,从单个消息消费队列去获取消息。

TransactionalMessageServiceImpl#check
long startTime = System.currentTimeMillis();
MessageQueue opQueue = getOpQueue(messageQueue);
long halfOffset = transactionalMessageBridge.fetchConsumeOffset(messageQueue);
long opOffset = transactionalMessageBridge.fetchConsumeOffset(opQueue);
log.info("Before check, the queue={} msgOffset={} opOffset={}", messageQueue, halfOffset, opOffset);
if (halfOffset < 0 || opOffset < 0) {
     log.error("MessageQueue: {} illegal offset read: {}, op offset: {},skip this queue", messageQueue, halfOffset, opOffset);
     continue;
}

Step3:获取对应的操作队列,其主题为:RMQ_SYS_TRANS_OP_HALF_TOPIC,然后获取操作队列的消费进度、待操作的消费队列的消费进度,如果任意一小于0,忽略该消息队列,继续处理下一个队列。

TransactionalMessageServiceImpl#check
List<Long> doneOpOffset = new ArrayList<>();
HashMap<Long, Long> removeMap = new HashMap<>();
PullResult pullResult = fillOpRemoveMap(removeMap, opQueue, opOffset, halfOffset, doneOpOffset);
if (null == pullResult) {
      log.error("The queue={} check msgOffset={} with opOffset={} failed, pullResult is null",
      messageQueue, halfOffset, opOffset);
      continue;
}

Step4:调用fillOpRemoveMap主题填充removeMap、doneOpOffset数据结构,这里主要的目的是避免重复调用事务回查接口,这里说一下RMQ_SYS_TRANS_HALF_TOPIC、RMQ_SYS_TRANS_OP_HALF_TOPIC这两个主题的作用。
RMQ_SYS_TRANS_HALF_TOPIC:prepare消息的主题,事务消息首先先进入到该主题。
RMQ_SYS_TRANS_OP_HALF_TOPIC:当消息服务器收到事务消息的提交或回滚请求后,会将消息存储在该主题下。

TransactionalMessageServiceImpl#check
// single thread
int getMessageNullCount = 1;
long newOffset = halfOffset;
long i = halfOffset;                         // @1 
while (true) {                                   
if (System.currentTimeMillis() - startTime > MAX_PROCESS_TIME_LIM
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值