RocketMQ源码解析之事务消息(五)

846 阅读8分钟

事务消息

什么是事务消息 笔者这里就不说了 笔者主要是通过源码去描述 RocketMQ如何实现事务消息,在RocketMQ中事务消息的设计以及状态和源码实现.

如何使用

通过RocketMQ的源码中的example中的看下如何使用 主要涉及到TransactionMQProducerTransactionListener两个类。

public static void main(String[] args) throws MQClientException, InterruptedException {
    TransactionListener transactionListener = new TransactionListenerImpl();
    TransactionMQProducer producer = new TransactionMQProducer("TestTranscationProducerName");
    ... 省略部分代码
    producer.setTransactionListener(transactionListener);
    producer.start();

    String[] tags = new String[]{"TagA", "TagB", "TagC", "TagD", "TagE"};
    for (int i = 0; i < 10; i++) {
        try {
            Message msg =
                    new Message("TopicTest", tags[i % tags.length], "KEY" + i,
                            ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET));
            SendResult sendResult = producer.sendMessageInTransaction(msg, null);
            Thread.sleep(10);
        } catch (MQClientException | UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }
   ....
}

TransactionListener 中包含2个方法

//当事务prepare消息发送到broker后 会执行的方法
LocalTransactionState executeLocalTransaction(final Message msg, final Object arg);

// 当执行后事务消息 没有返回响应的时候 比如返回的是UN_KNOW状态的时候会执行此方法 其实就Broker对消息生产者的一个询问事务状态
LocalTransactionState checkLocalTransaction(final MessageExt msg);

public class TransactionListenerImpl implements TransactionListener {
    private AtomicInteger transactionIndex = new AtomicInteger(0);

    private ConcurrentHashMap<String, Integer> localTrans = new ConcurrentHashMap<>();



    @Override
    public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
        int value = transactionIndex.getAndIncrement();
        int status = value % 3;
        localTrans.put(msg.getTransactionId(), status);
        return LocalTransactionState.UNKNOW;
    }


    @Override
    public LocalTransactionState checkLocalTransaction(MessageExt msg) {

        Integer status = localTrans.get(msg.getTransactionId());
        if (null != status) {
            switch (status) {
                case 0:
                    return LocalTransactionState.UNKNOW;
                case 1:
                    return LocalTransactionState.COMMIT_MESSAGE;
                case 2:
                    return LocalTransactionState.ROLLBACK_MESSAGE;
                default:
                    return LocalTransactionState.COMMIT_MESSAGE;
            }
        }
        return LocalTransactionState.COMMIT_MESSAGE;
    }
}

上面代码是RocketMQ给的Example代码,主要是为说明 事务消息的一个用法

关键点在于TransactionMQProducer发送消息 和 TransactionListener一个事务消息的事件监听 可以看到有COMMIT_MESSAGE,ROLLBACK_MESSAGE,UNKNOW三种状态.

那么接下来我们主要将分析放在 TransactionMQProducerTransactionListener 以及状态之间的关系 如何工作的。

三种状态

COMMIT_MESSAGE commit消息 也就是代表事务完成 提交的一个消息
ROLLBACK_MESSAGE rollback消息 也就是回滚消息
UNKNOW UNKNOW是代表事件的一个check状态 需要broker询问到Producer 该消息是否完成

上面是整个事务消息所存在的三种状态 接下来我们看下具体的工作流程.

源码分析工作流程

TransactionMQProducer

我们通过源码分析事务消息的一个具体工作流程 就从发送消息的TransactionMQProducer#sendMessageInTransaction开始。

因为源码方法太长,笔者将主分支的代码留下 会删减其他不重要的判断和检查代码

public TransactionSendResult sendMessageInTransaction(final Message msg,
   final LocalTransactionExecuter localTransactionExecuter, final Object arg)
       ....
       //添加消息属性PROPERTY_TRANSACTION_PREPARED 为true 表示为事务消息
       MessageAccessor.putProperty(msg, MessageConst.PROPERTY_TRANSACTION_PREPARED, "true");
       //设置当前生产组
       MessageAccessor.putProperty(msg, MessageConst.PROPERTY_PRODUCER_GROUP, this.defaultMQProducer.getProducerGroup());
       
       ...
       SEND_OK: //发送成功后
       String transactionId = msg.getProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX);
       if (null != transactionId && !"".equals(transactionId)) {
   msg.setTransactionId(transactionId);
       }
       
       // 省略 localTransactionState 过时兼容的逻辑  已经是过时的 5.0之后会删除 这里就不说了。 ...
       
       if (transactionListener != null) {
            //执行本地 executeLocalTransaction 方法
           localTransactionState = transactionListener.executeLocalTransaction(msg, arg);
       }
       if (null == localTransactionState) {
           localTransactionState = LocalTransactionState.UNKNOW;
       }
       

    // 下面会详细说明这个方法
   this.endTransaction(msg, sendResult, localTransactionState, localException);


可以看到sendMessageInTransaction主要实现了 消息发送,添加事务消息的标志以及 发送成功后会执行executeLocalTransaction的方法返回对应的localTransactionStateendTransaction去执行。

public void endTransaction(
    final Message msg,
    final SendResult sendResult,
    final LocalTransactionState localTransactionState,
    final Throwable localException) throws RemotingException, MQBrokerException, InterruptedException, UnknownHostException {
    final MessageId id;
  
     //... 省略id 和 连接的处理
     
     //创建EndTransactionRequestHeader
    EndTransactionRequestHeader requestHeader = new EndTransactionRequestHeader();
     //设置事务ID
    requestHeader.setTransactionId(transactionId);
    // 设置事务消息对应的CommitLogOffset
    requestHeader.setCommitLogOffset(id.getOffset());
    
    // 设置事务消息的状态 对应我们执行executeLocalTransaction返回的状态
    switch (localTransactionState) {
        case COMMIT_MESSAGE:
            requestHeader.setCommitOrRollback(MessageSysFlag.TRANSACTION_COMMIT_TYPE);
            break;
        case ROLLBACK_MESSAGE:
            requestHeader.setCommitOrRollback(MessageSysFlag.TRANSACTION_ROLLBACK_TYPE);
            break;
        case UNKNOW:
            requestHeader.setCommitOrRollback(MessageSysFlag.TRANSACTION_NOT_TYPE);
            break;
        default:
            break;
    }
    
     // 一些hook操作
    doExecuteEndTransactionHook(msg, sendResult.getMsgId(), brokerAddr, localTransactionState, false);
   
    // 设置当前发送消息的生产者组 
   requestHeader.setProducerGroup(this.defaultMQProducer.getProducerGroup());
    // 设置对应队列的offset
    requestHeader.setTranStateTableOffset(sendResult.getQueueOffset());
    // 设置消息ID
    requestHeader.setMsgId(sendResult.getMsgId());
    // executeLocalTransaction 是否出现异常
    String remark = localException != null ? ("executeLocalTransactionBranch exception: " + localException.toString()) : null;
    ...
   //发送消息状态给Broker this.mQClientFactory.getMQClientAPIImpl().endTransactionOneway(brokerAddr, requestHeader, remark,
        this.defaultMQProducer.getSendMsgTimeout());
}

主流程的发送流程基本如下

1.通过TransactionMQProducer发送事务消息 设置对应事务的监听TransactionListener
2.sendMessageInTransaction 将消息标识为事务消息,发送成功后执行TransactionListener#executeLocalTransaction 返回对应事务消息的状态
3.封装EndTransactionRequestHeader消息类型发送给broker当前事务处理的状态

image.png

通过上面我们大致知道 TransactionListener#executeLocalTransaction 执行的实际,但是没有看到对应消息状态和整体逻辑的关系 所以接下来我们需要去broker中找到对应事务消息的处理

Broker

SendMessageProcessor

同样我们从Broker接受到事务消息开始分析SendMessageProcessor#asyncSendMessage开始分析. 下面带是从asyncSendMessage中摘抄出来的有关事务消息的处理

if (transFlag != null && Boolean.parseBoolean(transFlag)) {
    //判断当前Broker是否接收事务消息
   if (this.brokerController.getBrokerConfig().isRejectTransactionMessage()) {
       response.setCode(ResponseCode.NO_PERMISSION);
       response.setRemark(
               "the broker[" + this.brokerController.getBrokerConfig().getBrokerIP1()
                       + "] sending transaction message is forbidden");
       return CompletableFuture.completedFuture(response);
   }
   //通过TransactionalMessageService单独处理事务消息
   putMessageResult = this.brokerController.getTransactionalMessageService().asyncPrepareMessage(msgInner);
}else{
   // 其他消息交给DefaultMessageStore处理
   putMessageResult = this.brokerController.getMessageStore().asyncPutMessage(msgInner);
} 

return handlePutMessageResultFuture(putMessageResult, response, request, msgInner, responseHeader, mqtraceContext, ctx, queueIdInt);

通过上面代码看到事务消息的处理机制是单独由TransactionalMessageService处理 接下来就通过分析TransactionalMessageService查看消息的处理.

@Override
public CompletableFuture<PutMessageResult> asyncPrepareMessage(MessageExtBrokerInner messageInner) {
    return transactionalMessageBridge.asyncPutHalfMessage(messageInner);
}

可以看到在TransactionalMessageService又使用了TransactionalMessageBridge#asyncPutHalfMessage的方法处理消息 这里注意这个方法的名字HalfMessage 一半消息,我们经常说的HalfMessage其实就这里的意思。

继续往下看

public CompletableFuture<PutMessageResult> asyncPutHalfMessage(MessageExtBrokerInner messageInner) {
    return store.asyncPutMessage(parseHalfMessageInner(messageInner));
}
private MessageExtBrokerInner parseHalfMessageInner(MessageExtBrokerInner msgInner) {
    //保存原有消息的Topic
    MessageAccessor.putProperty(msgInner, MessageConst.PROPERTY_REAL_TOPIC, msgInner.getTopic());
    //保存原有消息的Queue
    MessageAccessor.putProperty(msgInner, MessageConst.PROPERTY_REAL_QUEUE_ID,
        String.valueOf(msgInner.getQueueId()));
    // 设置事务状态为 TRANSACTION_NOT_TYPE
    msgInner.setSysFlag(
        MessageSysFlag.resetTransactionValue(msgInner.getSysFlag(), MessageSysFlag.TRANSACTION_NOT_TYPE));
    // 设置Topic为  RMQ_SYS_TRANS_HALF_TOPIC    
    msgInner.setTopic(TransactionalMessageUtil.buildHalfTopic());
    // 设置队列ID为0
    msgInner.setQueueId(0);
    msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties()));
    return msgInner;
}

发现还是使用的DefaultMessageStore, 只不过添加一层消息的包装 和我们的延时消息做法差不多 都是将Topic和Queue转换成 特殊消息的Topic和Queue。

那么这个消息处理流程的第一步就能知道了

1.消息生产者生产事务消息发送给broker
2.Broker接收消息后将对应消息包装为HalfMessage消息(替换Topic 和 Queue) 存储至CommitLog
3.返回给生产者 发送成功
4.消息生产者 处理本地事务 TransactionListener#executeLocalTransaction
5.将对应消息的事物处理结果 EndTransactionRequestHeader 发送给Broker

分析到这里基本上面5步是可以确定,接下来就是主要分析 Broker接受到EndTransactionRequestHeader是如何处理不同状态的。

EndTransactionProcessor

processRequest方法是用来处理Producer发来的EndTransactionRequestHeader消息

... // 省略判断 以及日志消息

OperationResult result = new OperationResult();
// 当状态为TRANSACTION_COMMIT_TYPE 的时候
if (MessageSysFlag.TRANSACTION_COMMIT_TYPE == requestHeader.getCommitOrRollback()) {

    //通过offset 获取之前存储的HalfMessage 也就是转换过Topic的消息
    result = this.brokerController.getTransactionalMessageService().commitMessage(requestHeader);
    if (result.getResponseCode() == ResponseCode.SUCCESS) {
        // 检查消息跟 提交过来的参数是否匹配
        RemotingCommand res = checkPrepareMessage(result.getPrepareMessage(), requestHeader);
        if (res.getCode() == ResponseCode.SUCCESS) {
            //将之前的HalfMessage 转换为 MessageExtBrokerInner
            MessageExtBrokerInner msgInner = endMessageTransaction(result.getPrepareMessage());
            //清除事务状态标识
            msgInner.setSysFlag(MessageSysFlag.resetTransactionValue(msgInner.getSysFlag(), requestHeader.getCommitOrRollback()));
            //设置TranStateTable偏移量
            msgInner.setQueueOffset(requestHeader.getTranStateTableOffset());
            //设置Half消息的偏移量
            msgInner.setPreparedTransactionOffset(requestHeader.getCommitLogOffset());
            //设置存储时间与Half消息一致
            msgInner.setStoreTimestamp(result.getPrepareMessage().getStoreTimestamp());
            //清除事务消息属性数据
            MessageAccessor.clearProperty(msgInner, MessageConst.PROPERTY_TRANSACTION_PREPARED);
            //存储消息到CommitLog中
            RemotingCommand sendResult = sendFinalMessage(msgInner);
            if (sendResult.getCode() == ResponseCode.SUCCESS) {
                //删除Half消息 这里需要注意 并不是真正的删除 只是添加一个消息 用来标识 Half已经被删除
                this.brokerController.getTransactionalMessageService().deletePrepareMessage(result.getPrepareMessage());
            }
            return sendResult;
        }
        return res;
    }
} else if (MessageSysFlag.TRANSACTION_ROLLBACK_TYPE == requestHeader.getCommitOrRollback()) {

    //通过offset 获取之前存储的HalfMessage 也就是转换过Topic的消息
    result = this.brokerController.getTransactionalMessageService().rollbackMessage(requestHeader);
    if (result.getResponseCode() == ResponseCode.SUCCESS) {
        //校验消息
        RemotingCommand res = checkPrepareMessage(result.getPrepareMessage(), requestHeader);
        if (res.getCode() == ResponseCode.SUCCESS) {
            //删除Half消息 和Commit是一样的 只是添加一条消息用来标识 Half被删除
            this.brokerController.getTransactionalMessageService().deletePrepareMessage(result.getPrepareMessage());
        }
        return res;
    }
}
...

可以通过上面看到 处理了两种状态值 TRANSACTION_COMMIT_TYPETRANSACTION_ROLLBACK_TYPE 处理步骤如下:

1.通过offset 找到之前的 Half消息也就是转换过Topic 和 Queue的消息
2.如果是COMMIT消息则将Half消息还原为之前的Message并存储到CommitLog中
3.添加一条消息用来标志Half消息已被删除

这里还需要说明下deletePrepareMessage

最终会调用到addRemoveTagInTransactionOp 这个方法中 可以看到OP中的body存储的是Half消息队列的Offset

主要OP消息的Body为messageExt.getQueueOffset()

private boolean addRemoveTagInTransactionOp(MessageExt messageExt, MessageQueue messageQueue) {
  
   Message message = new Message(TransactionalMessageUtil.buildOpTopic(), TransactionalMessageUtil.REMOVETAG,
       String.valueOf(messageExt.getQueueOffset()).getBytes(TransactionalMessageUtil.charset));
   writeOp(message, messageQueue);
   return true;
}

需要区分下两个Topic这个比较重要

RMQ_SYS_TRANS_OP_HALF_TOPIC
public static String buildOpTopic() {
    return TopicValidator.RMQ_SYS_TRANS_OP_HALF_TOPIC;
}

RMQ_SYS_TRANS_HALF_TOPIC
public static String buildHalfTopic() {
    return TopicValidator.RMQ_SYS_TRANS_HALF_TOPIC;
}

RMQ_SYS_TRANS_HALF_TOPIC 这个是消息转换为事务消息存储的 Topic 也就是 当事务消息发送过来 转换的Topic为 buildHalfTopic()这个方法。
RMQ_SYS_TRANS_OP_HALF_TOPIC 而这个消息 是在消息EndTransactionProcessor中回传了 COMMIT或则ROLLBACK的时候 新增加的一条删除Half消息的标识 可以理解为记录half消息的处理状态的一条消息 这个在下面的Check会详细说明

这样就可以完善事务消息的一些状态处理,但是我们一直都没看到一种状态叫UNKNOW,那么如果返回的是UNKNOW状态 Broker又是如何处理的呢?

这个就要从 TransactionalMessageService去查看如何处理UNKNOW的状态

TransactionalMessageService

笔者上面说过TransactionalMessageService#prepareMessage方法是用来将消息包装成Half的消息。

那么如果一直没有收到COMMITROLLBACK 我们通过check方法来查看,我们在网上看到的check的状态指的就是UNKNOW的状态或者说是prepare的状态,主要实现逻辑在check方法中

首先我们确定check方法运行的时机. 在TransactionalMessageCheckService中会执行到check方法 并且 TransactionalMessageCheckService是继承于ServiceThread的 代码如下

@Override
public void run() {
    log.info("Start transaction check service thread!");
    long checkInterval = brokerController.getBrokerConfig().getTransactionCheckInterval();
    while (!this.isStopped()) {
        this.waitForRunning(checkInterval);
    }
    log.info("End transaction check service thread!");
}


@Override
protected void onWaitEnd() {
    // 超时时间
    long timeout = brokerController.getBrokerConfig().getTransactionTimeOut();
    // 最大询问次数
    int checkMax = brokerController.getBrokerConfig().getTransactionCheckMax();
    // 开始执行时间
    long begin = System.currentTimeMillis();
    log.info("Begin to check prepare message, begin time:{}", begin);
    // 执行TransactionalMessageService的check方法
    this.brokerController.getTransactionalMessageService().check(timeout, checkMax, this.brokerController.getTransactionalMessageCheckListener());
    log.info("End to check prepare message, consumed time:{}", System.currentTimeMillis() - begin);
}
}

通过上面代码我们可以看到 是根据transactionCheckInterval配置每隔多长时间执行一次的,也就是说相当于一个定时器一样 隔一段时间执行一次 隔一段时间执行一次 , 调用service的时候 传递了超时时间 最大询问次数 和 TransactionalMessageCheckListener

我们先分析下 this.brokerController.getTransactionalMessageCheckListener()是什么? 对应到实现类DefaultTransactionalMessageCheckListener 包含几个比较重要的方法 我这里先列出来

resolveHalfMsg 发送check消息给Producer

public void resolveHalfMsg(final MessageExt msgExt) {
    executorService.execute(new Runnable() {
        @Override
        public void run() {
            try {
                //发送check消息给Producer
                sendCheckMessage(msgExt);
            } catch (Exception e) {
                LOGGER.error("Send check message error!", e);
            }
        }
    });
}

resolveDiscardMsg 丢弃消息 通过时间和次数判断的事务消息超时或者超过最大check次数 采取丢弃消息的策略

@Override
public void resolveDiscardMsg(MessageExt msgExt) {
    
    try {
        //将消息存的Topic改为 TRANS_CHECK_MAX_TIME_TOPIC 写入Commitlog 相当于丢弃消息的操作了
        MessageExtBrokerInner brokerInner = toMessageExtBrokerInner(msgExt);
        PutMessageResult putMessageResult = this.getBrokerController().getMessageStore().putMessage(brokerInner);
        if (putMessageResult != null && putMessageResult.getPutMessageStatus() == PutMessageStatus.PUT_OK) {
           。。。
    } catch (Exception e) {
        log.warn("Put checked-too-many-time message to TRANS_CHECK_MAXTIME_TOPIC error. {}", e);
    }

}

接下来我们分析check方法中的实现细节,check这个方法相对来说 实现的比较长 所以我将内部的源码关键点提出来一个一个分析。

  1. 通过Topic获取对应事务的MessageQueue
String topic = TopicValidator.RMQ_SYS_TRANS_HALF_TOPIC;
Set<MessageQueue> msgQueues = transactionalMessageBridge.fetchMessageQueues(topic);
  1. 遍历获取MessageQueue两种不同的Topic消费的偏移量 RMQ_SYS_TRANS_OP_HALF_TOPICRMQ_SYS_TRANS_HALF_TOPIC
for (MessageQueue messageQueue : msgQueues) {
long halfOffset = transactionalMessageBridge.fetchConsumeOffset(messageQueue);
long opOffset = transactionalMessageBridge.fetchConsumeOffset(opQueue);
...
}
  1. 通过OP消息将那些属于需要删除的消息记录到removeMap中 具体实现如下
List<Long> doneOpOffset = new ArrayList<>();
HashMap<Long, Long> removeMap = new HashMap<>();
PullResult pullResult = fillOpRemoveMap(removeMap, opQueue, opOffset, halfOffset, doneOpOffset);
private PullResult fillOpRemoveMap(HashMap<Long, Long> removeMap,
    MessageQueue opQueue, long pullOffsetOfOp, long miniOffset, List<Long> doneOpOffset) {
    //通过队列偏移量获取对应OP具体消息
    PullResult pullResult = pullOpMsg(opQueue, pullOffsetOfOp, 32);
     ... 删除部分判断   
    List<MessageExt> opMsg = pullResult.getMsgFoundList();
    //遍历获取OP消息
    for (MessageExt opMessageExt : opMsg) {
        //通过OP消息可以得到 Half消息在队列中的偏移量
        Long queueOffset = getLong(new String(opMessageExt.getBody(), TransactionalMessageUtil.charset));
         
         // ....
         
        if (TransactionalMessageUtil.REMOVETAG.equals(opMessageExt.getTags())) {
            //当标识这个Half消息已经被删除
            if (queueOffset < miniOffset) {
                //记录到已处理过的List中
                doneOpOffset.add(opMessageExt.getQueueOffset());
            } else {
                // 添加到需要删除的Map中
                removeMap.put(queueOffset, opMessageExt.getQueueOffset());
            }
        }
    }
    return pullResult;
}
  1. 我们可以通过以上几步确认我们需要检查的数据 那些已经删除过了 那么下面代码就是一个循环操作,处理每一个Half消息,向Producer去询问消息以及丢弃消息的逻辑。
while (true) {
    ...
    if (removeMap.containsKey(i)) {
        log.debug("Half offset {} has been committed/rolled back", i);
        Long removedOpOffset = removeMap.remove(i);
        // 已经有删除标识的偏移量 加入到已处理的集合中
        doneOpOffset.add(removedOpOffset);
    } else {
        // 通过offset 和 具体的队列 topic 去commitlog中获取消息
        GetResult getResult = getHalfMsg(messageQueue, i);
        MessageExt msgExt = getResult.getMsg();
        if (msgExt == null) {
            if (getMessageNullCount++ > MAX_RETRY_COUNT_WHEN_HALF_NULL) {
                break;
            }
            if (getResult.getPullResult().getPullStatus() == PullStatus.NO_NEW_MSG) {
                log.debug("No new msg, the miss offset={} in={}, continue check={}, pull result={}", i,
                        messageQueue, getMessageNullCount, getResult.getPullResult());
                break;
            } else {
                log.info("Illegal offset, the miss offset={} in={}, continue check={}, pull result={}",
                        i, messageQueue, getMessageNullCount, getResult.getPullResult());
                i = getResult.getPullResult().getNextBeginOffset();
                newOffset = i;
                continue;
            }
        }

        // needDiscard 需要丢弃判断 并且同时增加了一次次数  check次数判断  或者 needSkip 超过消息预留的时间
        if (needDiscard(msgExt, transactionCheckMax) || needSkip(msgExt)) {
            // 丢弃消息 上面有描述这个方法的实现
            listener.resolveDiscardMsg(msgExt);
            newOffset = i + 1;
            i++;
            continue;
        }
        if (msgExt.getStoreTimestamp() >= startTime) {
            log.debug("Fresh stored. the miss offset={}, check it later, store={}", i,
                    new Date(msgExt.getStoreTimestamp()));
            break;
        }

        ....


        List<MessageExt> opMsg = pullResult.getMsgFoundList();
        //当先运行时间-消息生产的时间 超过了每次检查设置的时间 单条消息可以单独设置 没有设置默认就是整个执行超时的时间
        boolean isNeedCheck = (opMsg == null && valueOfCurrentMinusBorn > checkImmunityTime)
                //上面代码执行时间超过了单次运行超时时间
                || (opMsg != null && (opMsg.get(opMsg.size() - 1).getBornTimestamp() - startTime > transactionTimeout))
                // 当前时间小于生产时间
                || (valueOfCurrentMinusBorn <= -1);

        if (isNeedCheck) {
            // 将Half消息再次写入到commitlog中
            if (!putBackHalfMsgQueue(msgExt, i)) {
                continue;
            }
            //发送检查事务消息给Producer
            listener.resolveHalfMsg(msgExt);
        } else {
            //当不执行check的时候 再次计算 removeMap
            pullResult = fillOpRemoveMap(removeMap, opQueue, pullResult.getNextBeginOffset(), halfOffset, doneOpOffset);
            log.debug("The miss offset:{} in messageQueue:{} need to get more opMsg, result is:{}", i,
                    messageQueue, pullResult);
            continue;
        }
    }
    newOffset = i + 1;
    i++;
}

关于消费进度的保存和计算 不在本篇文章的重点中 后面会整理整个Rocket中的Offset的文章说明 这里简单看下配置文件 consumerOffset的配置.

{
   "offsetTable":{
       ....
      "RMQ_SYS_TRANS_HALF_TOPIC@CID_RMQ_SYS_TRANS":{0:132
      },
      "RMQ_SYS_TRANS_OP_HALF_TOPIC@CID_RMQ_SYS_TRANS":{0:50
      }
   }
}

总结

上面我们基本可以将 生产者发送事务消息到Broker处理消息 以及事务的几个状态的关系 以及 工作的机制的源码都分析了一次,下面我们将整个事务的流程进行梳理

整个流程差不多如下图

image.png