參考官方設計文檔:事務消息設計
文章目錄
事務消息流程圖
步驟流程
- 發送 half 消息給 Broker。
- 服務端響應消息寫入結果。
- 根據發送結果執行本地事務(如果寫入失敗,此時 half 消息對業務不可見,本地邏輯不執行)。
- 根據本地事務狀態執行 Commit 或者 Rollback(Commit 操作生成消息索引,消息對消費者可見)。
- 對沒有Commit/Rollback的事務消息(pending狀態的消息),從服務端發起一次“回查”。
- Producer收到回查消息,檢查回查消息對應的本地事務的狀態。
- 根據本地事務狀態,重新Commit或者Rollback。
設計關鍵點
- half 消息存儲在 RMQ_SYS_TRANS_HALF_TOPIC 主題中,消費者不可見。
- 開啓一個定時任務消費此 half 消息,向 Producer 回查執行結果,重新發送一條消息到 HALF 主題,HALF 主題消費隊列偏移量推進。
- 確定 half 消息 Commit 或者 Rollback 後,將對應的處理結果消息(存放的是 HALF 消費隊列的偏移量 )放到 RMQ_SYS_TRANS_OP_HALF_TOPIC 主題中。
- half 消息不會刪除,OP_HALF 主題中存在 half 的消息結果代表 half 消息已被處理。
- Commit 消息還會恢復原消息,發送到真實的主題下,這樣消費者就能消費了。
- 超過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。