RocketMQ源碼分析——事務消息

參考官方設計文檔:事務消息設計

事務消息流程圖

在這裏插入圖片描述

步驟流程

  1. 發送 half 消息給 Broker。
  2. 服務端響應消息寫入結果。
  3. 根據發送結果執行本地事務(如果寫入失敗,此時 half 消息對業務不可見,本地邏輯不執行)。
  4. 根據本地事務狀態執行 Commit 或者 Rollback(Commit 操作生成消息索引,消息對消費者可見)。
  5. 對沒有Commit/Rollback的事務消息(pending狀態的消息),從服務端發起一次“回查”。
  6. Producer收到回查消息,檢查回查消息對應的本地事務的狀態。
  7. 根據本地事務狀態,重新Commit或者Rollback。

設計關鍵點

  1. half 消息存儲在 RMQ_SYS_TRANS_HALF_TOPIC 主題中,消費者不可見。
  2. 開啓一個定時任務消費此 half 消息,向 Producer 回查執行結果,重新發送一條消息到 HALF 主題,HALF 主題消費隊列偏移量推進。
  3. 確定 half 消息 Commit 或者 Rollback 後,將對應的處理結果消息(存放的是 HALF 消費隊列的偏移量 )放到 RMQ_SYS_TRANS_OP_HALF_TOPIC 主題中。
  4. half 消息不會刪除,OP_HALF 主題中存在 half 的消息結果代表 half 消息已被處理。
  5. Commit 消息還會恢復原消息,發送到真實的主題下,這樣消費者就能消費了。
  6. 超過15次回查仍不能確定狀態,或者 CommitLog 文件超過 72h 過期,回滾此消息。
    在這裏插入圖片描述

接下來按照流程對照源碼一步步分析

詳細步驟

步驟1:事務消息的發送

org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl#sendMessageInTransaction
發送消息前設置兩個屬性,標記是事務消息,存儲生產者組

MessageAccessor.putProperty(msg, MessageConst.PROPERTY_TRANSACTION_PREPARED, "true");
MessageAccessor.putProperty(msg, MessageConst.PROPERTY_PRODUCER_GROUP, this.defaultMQProducer.getProducerGroup());

同步發送消息

this.sendDefaultImpl(msg, CommunicationMode.SYNC, null, timeout);

設置消息系統狀態

final String tranMsg = msg.getProperty(MessageConst.PROPERTY_TRANSACTION_PREPARED);
if (tranMsg != null && Boolean.parseBoolean(tranMsg)) {
    sysFlag |= MessageSysFlag.TRANSACTION_PREPARED_TYPE;
}

步驟2:Broker寫Half消息

org.apache.rocketmq.broker.processor.SendMessageProcessor
接收並處理消息請求,判斷是否是事務消息

String traFlag = oriProps.get(MessageConst.PROPERTY_TRANSACTION_PREPARED);
if (traFlag != null && Boolean.parseBoolean(traFlag)) {
    if (this.brokerController.getBrokerConfig().isRejectTransactionMessage()) {
    	// 不能處理直接返回
        return response;
    }
    // 事務消息特殊處理
    putMessageResult = this.brokerController.getTransactionalMessageService().prepareMessage(msgInner);
} else {
    putMessageResult = this.brokerController.getMessageStore().putMessage(msgInner);
}

org.apache.rocketmq.broker.transaction.queue.TransactionalMessageBridge#putHalfMessage
解析出 Half 消息並存儲

public PutMessageResult putHalfMessage(MessageExtBrokerInner messageInner) {
    return store.putMessage(parseHalfMessageInner(messageInner));
}

解析過程:把真實主題和隊列放到屬性中,重置系統狀態,重設主題爲 RMQ_SYS_TRANS_HALF_TOPIC,重設隊列爲 0
存儲到 CommitLog 中,此消息無法被消費者消費。

private MessageExtBrokerInner parseHalfMessageInner(MessageExtBrokerInner msgInner) {
    MessageAccessor.putProperty(msgInner, MessageConst.PROPERTY_REAL_TOPIC, msgInner.getTopic());
    MessageAccessor.putProperty(msgInner, MessageConst.PROPERTY_REAL_QUEUE_ID,
        String.valueOf(msgInner.getQueueId()));
    msgInner.setSysFlag(
        MessageSysFlag.resetTransactionValue(msgInner.getSysFlag(), MessageSysFlag.TRANSACTION_NOT_TYPE));
    msgInner.setTopic(TransactionalMessageUtil.buildHalfTopic());
    msgInner.setQueueId(0);
    msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties()));
    return msgInner;
}

步驟3:執行本地事務

同步調用消息發送接口,等待Broker返回執行結果,異常就拋出錯誤不執行後續本地事務

SendResult sendResult = null;
try {
    sendResult = this.send(msg);
} catch (Exception e) {
    throw new MQClientException("send message Exception", e);
}

若返回成功狀態,執行本地事務。若返回失敗狀態,包括刷盤超時、同步Slave超時、Slave不可用,不執行本地事務,並標記本地事務執行狀態爲回滾。

LocalTransactionState localTransactionState = LocalTransactionState.UNKNOW;
switch (sendResult.getSendStatus()) {
    case SEND_OK: {
        try {
            ...
            localTransactionState = transactionListener.executeLocalTransaction(msg, arg);
            // 本地事務無返回狀態,默認未知
            if (null == localTransactionState) {
                localTransactionState = LocalTransactionState.UNKNOW;
            }
            ...
    }
    break;
    case FLUSH_DISK_TIMEOUT:
    case FLUSH_SLAVE_TIMEOUT:
    case SLAVE_NOT_AVAILABLE:
        localTransactionState = LocalTransactionState.ROLLBACK_MESSAGE;
        break;
    default:
        break;
}

按照本地事務的執行結果,向Broker發送 Commit 或者 Rollback 請求。

try {
    this.endTransaction(sendResult, localTransactionState, localException);
} catch (Exception e) {
}

RemotingCommand 命令代碼:END_TRANSACTION,發送方式 Oneway。發送失敗也無所謂,後續 Broker 會回查本地事務狀態。

EndTransactionRequestHeader requestHeader = new EndTransactionRequestHeader();
requestHeader.setTransactionId(transactionId);
requestHeader.setCommitLogOffset(id.getOffset());
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;
}
this.mQClientFactory.getMQClientAPIImpl().endTransactionOneway(brokerAddr, requestHeader, remark,
            this.defaultMQProducer.getSendMsgTimeout());

步驟4:Broker提交或者回滾Half消息

Broker 接收 END_TRANSACTION 命令
org.apache.rocketmq.broker.processor.EndTransactionProcessor#processRequest
非 Commit 或者 Rollback 不處理

switch (requestHeader.getCommitOrRollback()) {
    case MessageSysFlag.TRANSACTION_NOT_TYPE: {
        return null;
    }
    case MessageSysFlag.TRANSACTION_COMMIT_TYPE: {
        break;
    }
    case MessageSysFlag.TRANSACTION_ROLLBACK_TYPE: {
        break;
    }
    default:
        return null;
}

處理 Commit 或者 Rollback

OperationResult result = new OperationResult();
if (MessageSysFlag.TRANSACTION_COMMIT_TYPE == requestHeader.getCommitOrRollback()) {
	// 獲取 Half 消息
    result = this.brokerController.getTransactionalMessageService().commitMessage(requestHeader);
    // 是否獲取成功
    if (result.getResponseCode() == ResponseCode.SUCCESS) {
    	// 驗證 Half 消息是否一致
        RemotingCommand res = checkPrepareMessage(result.getPrepareMessage(), requestHeader);
        if (res.getCode() == ResponseCode.SUCCESS) {
        	// 根據 Half 消息恢復出原消息
            MessageExtBrokerInner msgInner = endMessageTransaction(result.getPrepareMessage());
            msgInner.setSysFlag(MessageSysFlag.resetTransactionValue(msgInner.getSysFlag(), requestHeader.getCommitOrRollback()));
            msgInner.setQueueOffset(requestHeader.getTranStateTableOffset());
            msgInner.setPreparedTransactionOffset(requestHeader.getCommitLogOffset());
            msgInner.setStoreTimestamp(result.getPrepareMessage().getStoreTimestamp());
            // 原消息重新進入 CommitLog,讓消費者正常消費
            RemotingCommand sendResult = sendFinalMessage(msgInner);
            if (sendResult.getCode() == ResponseCode.SUCCESS) {
                // 刷盤成功,刪除 Half 消息
                this.brokerController.getTransactionalMessageService().deletePrepareMessage(result.getPrepareMessage());
            }
            // 失敗,等待後續回查
            return sendResult;
        }
        return res;
    }
} else if (MessageSysFlag.TRANSACTION_ROLLBACK_TYPE == requestHeader.getCommitOrRollback()) {
    result = this.brokerController.getTransactionalMessageService().rollbackMessage(requestHeader);
    if (result.getResponseCode() == ResponseCode.SUCCESS) {
        RemotingCommand res = checkPrepareMessage(result.getPrepareMessage(), requestHeader);
        if (res.getCode() == ResponseCode.SUCCESS) {
        	// 刪除 Half 消息
            this.brokerController.getTransactionalMessageService().deletePrepareMessage(result.getPrepareMessage());
        }
        return res;
    }
}

獲取 Half 消息

public OperationResult commitMessage(EndTransactionRequestHeader requestHeader) {
    return getHalfMessageByOffset(requestHeader.getCommitLogOffset());
}

public OperationResult rollbackMessage(EndTransactionRequestHeader requestHeader) {
    return getHalfMessageByOffset(requestHeader.getCommitLogOffset());
}

根據 Half 消息 result 驗證生產者組、事務狀態、偏移量是否和請求傳過來的一致

private RemotingCommand checkPrepareMessage(MessageExt msgExt, EndTransactionRequestHeader requestHeader) {
    final RemotingCommand response = RemotingCommand.createResponseCommand(null);
    if (msgExt != null) {
        final String pgroupRead = msgExt.getProperty(MessageConst.PROPERTY_PRODUCER_GROUP);
        if (!pgroupRead.equals(requestHeader.getProducerGroup())) {
            ...
        }

        if (msgExt.getQueueOffset() != requestHeader.getTranStateTableOffset()) {
            ...
        }

        if (msgExt.getCommitLogOffset() != requestHeader.getCommitLogOffset()) {
            ...
        }
    } else {
        response.setCode(ResponseCode.SYSTEM_ERROR);
        response.setRemark("Find prepared transaction message failed");
        return response;
    }
    response.setCode(ResponseCode.SUCCESS);
    return response;
}

Commit 消息提交,需要先恢復出原消息

private MessageExtBrokerInner endMessageTransaction(MessageExt msgExt) {
    MessageExtBrokerInner msgInner = new MessageExtBrokerInner();
    // 恢復屬性中的主題和隊列
    msgInner.setTopic(msgExt.getUserProperty(MessageConst.PROPERTY_REAL_TOPIC));
    msgInner.setQueueId(Integer.parseInt(msgExt.getUserProperty(MessageConst.PROPERTY_REAL_QUEUE_ID)));
    msgInner.setBody(msgExt.getBody());
    msgInner.setFlag(msgExt.getFlag());
    msgInner.setBornTimestamp(msgExt.getBornTimestamp());
    msgInner.setBornHost(msgExt.getBornHost());
    msgInner.setStoreHost(msgExt.getStoreHost());
    msgInner.setReconsumeTimes(msgExt.getReconsumeTimes());
    msgInner.setWaitStoreMsgOK(false);
    msgInner.setTransactionId(msgExt.getUserProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX));
    msgInner.setSysFlag(msgExt.getSysFlag());
    TopicFilterType topicFilterType =
        (msgInner.getSysFlag() & MessageSysFlag.MULTI_TAGS_FLAG) == MessageSysFlag.MULTI_TAGS_FLAG ? TopicFilterType.MULTI_TAG
            : TopicFilterType.SINGLE_TAG;
    long tagsCodeValue = MessageExtBrokerInner.tagsString2tagsCode(topicFilterType, msgInner.getTags());
    msgInner.setTagsCode(tagsCodeValue);
    MessageAccessor.setProperties(msgInner, msgExt.getProperties());
    msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgExt.getProperties()));
    // 清空屬性中的主題和隊列
    MessageAccessor.clearProperty(msgInner, MessageConst.PROPERTY_REAL_TOPIC);
    MessageAccessor.clearProperty(msgInner, MessageConst.PROPERTY_REAL_QUEUE_ID);
    return msgInner;
}

Commit 消息,重新將原消息存儲到 CommitLog,並返回存儲結果。Slave 的刷盤結果不影響此次消息成功返回

private RemotingCommand sendFinalMessage(MessageExtBrokerInner msgInner) {
    final RemotingCommand response = RemotingCommand.createResponseCommand(null);
    final PutMessageResult putMessageResult = this.brokerController.getMessageStore().putMessage(msgInner);
    if (putMessageResult != null) {
        switch (putMessageResult.getPutMessageStatus()) {
            // Success
            case PUT_OK:
            case FLUSH_DISK_TIMEOUT:
            case FLUSH_SLAVE_TIMEOUT:
            case SLAVE_NOT_AVAILABLE:
                response.setCode(ResponseCode.SUCCESS);
                response.setRemark(null);
                break;
            // Failed
            case CREATE_MAPEDFILE_FAILED:
            case MESSAGE_ILLEGAL:
            case PROPERTIES_SIZE_EXCEEDED:
            case SERVICE_NOT_AVAILABLE:
            case OS_PAGECACHE_BUSY:
            case UNKNOWN_ERROR:
            default:
                response.setCode(ResponseCode.SYSTEM_ERROR);
                response.setRemark("UNKNOWN_ERROR DEFAULT");
                break;
        }
        return response;
    } else {
        response.setCode(ResponseCode.SYSTEM_ERROR);
        response.setRemark("store putMessage return null");
    }
    return response;
}

原消息刷盤成功,刪除 Half 消息,整個事務消息結束

public boolean deletePrepareMessage(MessageExt msgExt) {
    if (this.transactionalMessageBridge.putOpMessage(msgExt, TransactionalMessageUtil.REMOVETAG)) {
        return true;
    } else {
        return false;
    }
}
public boolean putOpMessage(MessageExt messageExt, String opType) {
    MessageQueue messageQueue = new MessageQueue(messageExt.getTopic(),
        this.brokerController.getBrokerConfig().getBrokerName(), messageExt.getQueueId());
    if (TransactionalMessageUtil.REMOVETAG.equals(opType)) {
        return addRemoveTagInTransactionOp(messageExt, messageQueue);
    }
    return true;
}

Op 消息存儲的是 Half 消息的消費隊列偏移量

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;
}

步驟5:Broker定時回查

Broker 存儲了 Half 消息後,若收不到 Commit 或者 Rollback 命令,定時執行回查。

public class TransactionalMessageCheckService extends ServiceThread {
    public void run() {
    	// 默認間隔 60s
        long checkInterval = brokerController.getBrokerConfig().getTransactionCheckInterval();
        while (!this.isStopped()) {
            this.waitForRunning(checkInterval);
        }
    }

    @Override
    protected void onWaitEnd() {
    	// 默認一條消息存儲超過6s才執行回查
        long timeout = brokerController.getBrokerConfig().getTransactionTimeOut();
        // 一條消息最大回查次數,默認15次後刪除 Half 消息
        int checkMax = brokerController.getBrokerConfig().getTransactionCheckMax();
        this.brokerController.getTransactionalMessageService().check(timeout, checkMax, this.brokerController.getTransactionalMessageCheckListener());
    }
}

具體執行回查邏輯

public void check(long transactionTimeout, int transactionCheckMax,
    AbstractTransactionalMessageCheckListener listener) {
    try {
        String topic = MixAll.RMQ_SYS_TRANS_HALF_TOPIC;
        // 獲取到所有的 Half 消費隊列
        Set<MessageQueue> msgQueues = transactionalMessageBridge.fetchMessageQueues(topic);
        if (msgQueues == null || msgQueues.size() == 0) {
            log.warn("The queue of topic is empty :" + topic);
            return;
        }
        log.debug("Check topic={}, queues={}", topic, msgQueues);
        for (MessageQueue messageQueue : msgQueues) {
            long startTime = System.currentTimeMillis();
            // 根據 Half 隊列獲取到 Op 隊列,Broker名稱和隊列序號都一樣只是主題不一樣
            MessageQueue opQueue = getOpQueue(messageQueue);
            // 獲取到 Half 消息的消費進度偏移量
            long halfOffset = transactionalMessageBridge.fetchConsumeOffset(messageQueue);
            // 獲取到 Op 消息的消費進度偏移量
            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;
            }
			// 已處理的Op消息
            List<Long> doneOpOffset = new ArrayList<>();
            // 準備處理的 Half 消息
            HashMap<Long, Long> removeMap = new HashMap<>();
            // 對比兩個隊列,默認從 Op 主題拉取32條消息
            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;
            }
            // single thread
            int getMessageNullCount = 1;// 獲取空消息的次數
            long newOffset = halfOffset; // half消息的最新進度
            long i = halfOffset;
            while (true) {
                // 一次檢查任務只執行60s,然後退出等下個檢查任務去執行
                if (System.currentTimeMillis() - startTime > MAX_PROCESS_TIME_LIMIT) {
                    log.info("Queue={} process time reach max={}", messageQueue, MAX_PROCESS_TIME_LIMIT);
                    break;
                }
                // 消息已經被處理過了,跳過
                if (removeMap.containsKey(i)) {
                    log.info("Half offset {} has been committed/rolled back", i);
                    removeMap.remove(i);
                } else {
                    // 獲取half消息
                    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;
                        }
                    }

                    // 是否回查,次數超過15次丟棄,消息文件超過72h過期了跳過
                    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;
                    }

                    // 已存儲時間
                    long valueOfCurrentMinusBorn = System.currentTimeMillis() - msgExt.getBornTimestamp();
                    // 消息存儲超過6s纔回查
                    long checkImmunityTime = transactionTimeout;
                    String checkImmunityTimeStr = msgExt.getUserProperty(MessageConst.PROPERTY_CHECK_IMMUNITY_TIME_IN_SECONDS);
                    if (null != checkImmunityTimeStr) {
                        checkImmunityTime = getImmunityTime(checkImmunityTimeStr, transactionTimeout);
                        if (valueOfCurrentMinusBorn < checkImmunityTime) {
                            if (checkPrepareQueueOffset(removeMap, doneOpOffset, msgExt)) {
                                newOffset = i + 1;
                                i++;
                                continue;
                            }
                        }
                    } else {
                        if ((0 <= valueOfCurrentMinusBorn) && (valueOfCurrentMinusBorn < checkImmunityTime)) {
                            log.debug("New arrived, the miss offset={}, check it later checkImmunity={}, born={}", i,
                                checkImmunityTime, new Date(msgExt.getBornTimestamp()));
                            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;
                        }
                        // 向生產者發送查詢請求
                        listener.resolveHalfMsg(msgExt);
                    } else {
                        // 無法判斷,加載更多的op消息
                        pullResult = fillOpRemoveMap(removeMap, opQueue, pullResult.getNextBeginOffset(), halfOffset, doneOpOffset);
                        log.info("The miss offset:{} in messageQueue:{} need to get more opMsg, result is:{}", i,
                            messageQueue, pullResult);
                        continue;
                    }
                }
                newOffset = i + 1;
                i++;
            }
            // 保存回查進度
            if (newOffset != halfOffset) {
                transactionalMessageBridge.updateConsumeOffset(messageQueue, newOffset);
            }
            long newOpOffset = calculateOpOffset(doneOpOffset, opOffset);
            // 保存回查進度
            if (newOpOffset != opOffset) {
                transactionalMessageBridge.updateConsumeOffset(opQueue, newOpOffset);
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
        log.error("Check error", e);
    }

}

發送回查請求時,先將調用 putBackHalfMsgQueue 將 Half 消息再次存入 CommitLog,處理進度加一,MQ保證順序寫,無法真正的刪除消息。
然後開啓一個線程去執行回調查詢,不等待查詢結果,因爲Producer會發送一條處理結果回來。

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

構造發送請求,根據消息的生產者組名稱,從生產者組中輪詢選擇一個生產者發送回調查詢請求

public void sendCheckMessage(MessageExt msgExt) throws Exception {
    CheckTransactionStateRequestHeader checkTransactionStateRequestHeader = new CheckTransactionStateRequestHeader();
    checkTransactionStateRequestHeader.setCommitLogOffset(msgExt.getCommitLogOffset());
    checkTransactionStateRequestHeader.setOffsetMsgId(msgExt.getMsgId());
    checkTransactionStateRequestHeader.setMsgId(msgExt.getUserProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX));
    checkTransactionStateRequestHeader.setTransactionId(checkTransactionStateRequestHeader.getMsgId());
    checkTransactionStateRequestHeader.setTranStateTableOffset(msgExt.getQueueOffset());
    msgExt.setTopic(msgExt.getUserProperty(MessageConst.PROPERTY_REAL_TOPIC));
    msgExt.setQueueId(Integer.parseInt(msgExt.getUserProperty(MessageConst.PROPERTY_REAL_QUEUE_ID)));
    msgExt.setStoreSize(0);
    String groupId = msgExt.getProperty(MessageConst.PROPERTY_PRODUCER_GROUP);
    Channel channel = brokerController.getProducerManager().getAvaliableChannel(groupId);
    if (channel != null) {
        brokerController.getBroker2Client().checkProducerTransactionState(groupId, channel, checkTransactionStateRequestHeader, msgExt);
    } else {
        LOGGER.warn("Check transaction failed, channel is null. groupId={}", groupId);
    }
}

不管是生產者還是消費者都會向所有的Broker發送心跳,找到第一個有效的客戶端 Channel 通道

public Channel getAvaliableChannel(String groupId) {
    HashMap<Channel, ClientChannelInfo> channelClientChannelInfoHashMap = groupChannelTable.get(groupId);
    List<Channel> channelList = new ArrayList<Channel>();
    if (channelClientChannelInfoHashMap != null) {
        for (Channel channel : channelClientChannelInfoHashMap.keySet()) {
            channelList.add(channel);
        }
        int index = positiveAtomicCounter.incrementAndGet() % size;
        Channel channel = channelList.get(index);
        int count = 0;
        boolean isOk = channel.isActive() && channel.isWritable();
        while (count++ < GET_AVALIABLE_CHANNEL_RETRY_COUNT) {
            if (isOk) {
                return channel;
            }
            index = (++index) % size;
            channel = channelList.get(index);
            isOk = channel.isActive() && channel.isWritable();
        }
    } else {
        log.warn("Check transaction failed, channel table is empty. groupId={}", groupId);
        return null;
    }
    return null;
}

具體的 RemotingCommand 請求命令是 CHECK_TRANSACTION_STATE

步驟6:Producer查詢本地事務

生產者接收 Broker 回查請求,解析出 Broker 地址
org.apache.rocketmq.client.impl.ClientRemotingProcessor#checkTransactionState

final String addr = RemotingHelper.parseChannelRemoteAddr(ctx.channel());
// 檢查本地事務
producer.checkTransactionState(addr, messageExt, requestHeader);

生產者開啓了一個線程池用來處理 Broker 的回調查詢請求

public void checkTransactionState(final String addr, final MessageExt msg,
    final CheckTransactionStateRequestHeader header) {
    Runnable request = new Runnable() {
        private final String brokerAddr = addr;
        private final MessageExt message = msg;
        private final CheckTransactionStateRequestHeader checkRequestHeader = header;
        private final String group = DefaultMQProducerImpl.this.defaultMQProducer.getProducerGroup();

        @Override
        public void run() {
        	// 檢查本地事務監聽是否存在
            TransactionCheckListener transactionCheckListener = DefaultMQProducerImpl.this.checkListener();
            TransactionListener transactionListener = getCheckListener();
            if (transactionCheckListener != null || transactionListener != null) {
                LocalTransactionState localTransactionState = LocalTransactionState.UNKNOW;
                Throwable exception = null;
                try {
                	// 執行本地事務結果查詢,這裏是需要生產者實現的地方
                    if (transactionCheckListener != null) {
                		// 區分新舊版本,舊的事務接口已被標記棄用
                        localTransactionState = transactionCheckListener.checkLocalTransactionState(message);
                    } else if (transactionListener != null) {
                        log.debug("Used new check API in transaction message");
                        localTransactionState = transactionListener.checkLocalTransaction(message);
                    } else {
                        log.warn("CheckTransactionState, pick transactionListener by group[{}] failed", group);
                    }
                } catch (Throwable e) {
                    log.error("Broker call checkTransactionState, but checkLocalTransactionState exception", e);
                    exception = e;
                }
				// 按照本地查詢結果,再次處理 Broker 事務狀態
                this.processTransactionState(
                    localTransactionState,
                    group,
                    exception);
            } else {
                log.warn("CheckTransactionState, pick transactionCheckListener by group[{}] failed", group);
            }
        }

		// 和 endTransaction 方法類似
        private void processTransactionState(
            final LocalTransactionState localTransactionState,
            final String producerGroup,
            final Throwable exception) {
            ...
            try {
                DefaultMQProducerImpl.this.mQClientFactory.getMQClientAPIImpl().endTransactionOneway(brokerAddr, thisHeader, remark,
                    3000);
            } catch (Exception e) {
            }
        }
    };

    this.checkExecutor.submit(request);
}

步驟7:Broker重新提交或者回滾Half消息

再次調用 org.apache.rocketmq.broker.processor.EndTransactionProcessor#processRequest,邏輯一樣。

事務消息主題目錄
在這裏插入圖片描述

總結

事務消息先寫 Half 消息,對消息的 Topic 和 Queue 等屬性進行替換,同時將原來的 Topic 和 Queue 信息存儲到消息的屬性中,正因爲消息主題被替換,故消息並不會轉發到該原主題的消息消費隊列,消費者無法感知消息的存在,不會消費。

在完成一階段寫入一條對用戶不可見的消息後,二階段如果是 Commit 操作,則需要讓消息對用戶可見,恢復原消息重新存儲到 CommitLog,並刪除一階段的消息;如果是 Rollback 則需要撤銷一階段的消息。

RocketMQ 無法去真正的刪除一條消息,因爲是順序寫文件的。RocketMQ 使用 Op 消息標識事務消息已經確定的狀態(Commit或者Rollback)。Op 消息的主題是一個內部的 Topic(像Half消息的Topic一樣),不會被用戶消費。

Op 消息的內容爲對應的 Half 消息的存儲的Offset,這樣通過 Op 消息能索引到 Half 消息進行後續的回查操作。如果一條事務消息沒有對應的 Op 消息,說明這個事務的狀態還無法確定(可能是二階段失敗了)。對比 Half 消息和 Op 消息進行事務消息的回查並且推進處理進度。

不能確定的消息,Broker端會發起回查,將消息發送到對應的Producer端(同一個Group的Producer),由Producer根據消息來檢查本地事務的狀態,進而執行Commit或者Rollback。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章