文章基於rocket-mq4.3 代碼分析
事務消息發送
事務消息主要解決的問題是:本地事務與消息發送原子性的問題,通過消息異步消費(直到消費完成)達到生產端與消費端應用事務最終一致性。
如果要是有事務消息需要使用 TransactionMQProducer 這個類,下面摘自RocketMq源碼裏的測試例子
public class TransactionProducer {
public static void main(String[] args) throws MQClientException, InterruptedException {
TransactionListener transactionListener = new TransactionListenerImpl();
TransactionMQProducer producer = new TransactionMQProducer("please_rename_unique_group_name");
ExecutorService executorService = new ThreadPoolExecutor(2, 5, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(2000), new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setName("client-transaction-msg-check-thread");
return thread;
}
});
producer.setExecutorService(executorService);
//設置一個監聽,用戶本地事務的處理與接收broker超時回查
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("TopicTest1234", tags[i % tags.length], "KEY" + i,
("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET));
SendResult sendResult = producer.sendMessageInTransaction(msg, null);
System.out.printf("%s%n", sendResult);
Thread.sleep(10);
} catch (MQClientException | UnsupportedEncodingException e) {
e.printStackTrace();
}
}
//這裏sleep是爲了保證client能夠接收到broker的回調
for (int i = 0; i < 100000; i++) {
Thread.sleep(1000);
}
producer.shutdown();
}
}
TransactionListenerImpl 實現了 TransactionListener,該接口有兩個方法
public class TransactionListenerImpl implements TransactionListener {
private AtomicInteger transactionIndex = new AtomicInteger(0);
private ConcurrentHashMap<String, Integer> localTrans = new ConcurrentHashMap<>();
//這個方法會在client端發送完消息後根據消息發送的結果調用,是被client端自己調用的;
//這個方法被執行,表示client與broker之間是網絡暢通的,該方法主要編寫的功能是完成本地事務的處理
@Override
public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
int value = transactionIndex.getAndIncrement();
int status = value % 3;
localTrans.put(msg.getTransactionId(), status);
return LocalTransactionState.UNKNOW;
}
//這個方法是broker在超時後主動調用的,用於檢查本地事務是否已經完成,brokerg根據返回值決定是否需要投遞消息
@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;
}
}
return LocalTransactionState.COMMIT_MESSAGE;
}
}
摘自網上一個事務消息處理流程圖:
在發送消息階段,事務消息會被加上一點標記,這是它與普通消息差異的地方
MessageAccessor.putProperty(msg, MessageConst.PROPERTY_TRANSACTION_PREPARED, "true");
MessageAccessor.putProperty(msg, MessageConst.PROPERTY_PRODUCER_GROUP, this.defaultMQProducer.getProducerGroup());
broker在接收到這樣的消息後會調用
org.apache.rocketmq.broker.processor.SendMessageProcessor#sendMessage方法即消息通用保存流程(後續會有flushDisk、HA、cousumeQueus數據增加,索引重建等動作)
private RemotingCommand sendMessage(final ChannelHandlerContext ctx,
final RemotingCommand request,
final SendMessageContext sendMessageContext,
final SendMessageRequestHeader requestHeader) throws RemotingCommandException {
//省略一大波代碼........
//校驗是否有事務消息屬性 PROPERTY_TRANSACTION_PREPARED
String traFlag = oriProps.get(MessageConst.PROPERTY_TRANSACTION_PREPARED);
if (traFlag != null && Boolean.parseBoolean(traFlag)) {
//如果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 response;
}
//保存消息(調用的是TransactionalMessageServiceImpl實現類)
putMessageResult = this.brokerController.getTransactionalMessageService().prepareMessage(msgInner);
} else {
putMessageResult = this.brokerController.getMessageStore().putMessage(msgInner);
}
return handlePutMessageResult(putMessageResult, response, request, msgInner, responseHeader, sendMessageContext, ctx, queueIdInt);
}
TransactionalMessageServiceImpl然後調用org.apache.rocketmq.broker.transaction.queue.TransactionalMessageBridge#parseHalfMessageInner方法
//調用defaultmesgstroe保存消息
public PutMessageResult putHalfMessage(MessageExtBrokerInner messageInner) {
return store.putMessage(parseHalfMessageInner(messageInner));
}
//將原有消息蓋頭換面
private MessageExtBrokerInner parseHalfMessageInner(MessageExtBrokerInner msgInner) {
//將原始消息的topic和queueid都備份出來
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));
//設置新的topic和queueId
msgInner.setTopic(TransactionalMessageUtil.buildHalfTopic());
msgInner.setQueueId(0);
msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties()));
return msgInner;
}
後面就會走正常流程將消息保存進入commitlog中,保存的topic名稱是 RMQ_SYS_TRANS_HALF_TOPIC,queueId是0;
消息保存完畢後,broker向client返回結果,client接收到結果後根據這個結果判斷是否要執行本地事務;
//如果broker返回消息保存成功的消息
switch (sendResult.getSendStatus()) {
case SEND_OK: {
try {
if (sendResult.getTransactionId() != null) {
msg.putUserProperty("__transactionId__", sendResult.getTransactionId());
}
String transactionId = msg.getProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX);
if (null != transactionId && !"".equals(transactionId)) {
msg.setTransactionId(transactionId);
}
if (null != localTransactionExecuter) {
localTransactionState = localTransactionExecuter.executeLocalTransactionBranch(msg, arg);
} else if (transactionListener != null) {
//調用transactionListener執行client端本地事務,並獲得返回值 localTransactionState
log.debug("Used new transaction API");
localTransactionState = transactionListener.executeLocalTransaction(msg, arg);
}
if (null == localTransactionState) {
localTransactionState = LocalTransactionState.UNKNOW;
}
if (localTransactionState != LocalTransactionState.COMMIT_MESSAGE) {
log.info("executeLocalTransactionBranch return {}", localTransactionState);
log.info(msg.toString());
}
} catch (Throwable e) {
log.info("executeLocalTransactionBranch exception", e);
log.info(msg.toString());
localException = e;
}
}
break;
case FLUSH_DISK_TIMEOUT:
case FLUSH_SLAVE_TIMEOUT:
case SLAVE_NOT_AVAILABLE:
localTransactionState = LocalTransactionState.ROLLBACK_MESSAGE;
break;
default:
break;
}
接下來就是事務的收尾階段,調用的流程是:
org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl#endTransaction
↓
org.apache.rocketmq.client.impl.MQClientAPIImpl#endTransactionOneway(RequestCode爲:RequestCode.END_TRANSACTION)
↓
org.apache.rocketmq.remoting.netty.NettyRemotingClient#invokeOneway
調用流程來到broker端,處理該調用的Processor是org.apache.rocketmq.broker.processor.EndTransactionProcessor
我們分析一下事務提交部分的代碼,回滾的代碼先不分析
if (MessageSysFlag.TRANSACTION_COMMIT_TYPE == requestHeader.getCommitOrRollback()) {
result = this.brokerController.getTransactionalMessageService().commitMessage(requestHeader);
if (result.getResponseCode() == ResponseCode.SUCCESS) {
RemotingCommand res = checkPrepareMessage(result.getPrepareMessage(), requestHeader);
if (res.getCode() == ResponseCode.SUCCESS) {
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());
//將消息原有的屬性恢復並重新執行sendMessage的流程
RemotingCommand sendResult = sendFinalMessage(msgInner);
if (sendResult.getCode() == ResponseCode.SUCCESS) {
this.brokerController.getTransactionalMessageService().deletePrepareMessage(result.getPrepareMessage());
}
return sendResult;
}
return res;
}
}
我們看到這裏的做法跟定時消息處理的機制差不多,都是先將消息查詢出來,恢復原有的屬性(topic,queueid等)再將消息保存一遍,這樣消費端就能消費了。
事務回查
在broker的 initialize() 方法最後的地方會設置事務消息回查相關的服務類
private void initialTransaction() {
this.transactionalMessageService = ServiceProvider.loadClass(ServiceProvider.TRANSACTION_SERVICE_ID, TransactionalMessageService.class);
if (null == this.transactionalMessageService) {
this.transactionalMessageService = new TransactionalMessageServiceImpl(new TransactionalMessageBridge(this, this.getMessageStore()));
log.warn("Load default transaction message hook service: {}", TransactionalMessageServiceImpl.class.getSimpleName());
}
this.transactionalMessageCheckListener = ServiceProvider.loadClass(ServiceProvider.TRANSACTION_LISTENER_ID, AbstractTransactionalMessageCheckListener.class);
if (null == this.transactionalMessageCheckListener) {
this.transactionalMessageCheckListener = new DefaultTransactionalMessageCheckListener();
log.warn("Load default discard message hook service: {}", DefaultTransactionalMessageCheckListener.class.getSimpleName());
}
this.transactionalMessageCheckListener.setBrokerController(this);
this.transactionalMessageCheckService = new TransactionalMessageCheckService(this);
}
TransactionalMessageCheckService是一個線程類,最終會不斷執行 onWaitEnd() 方法
@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);
this.brokerController.getTransactionalMessageService().check(timeout, checkMax, this.brokerController.getTransactionalMessageCheckListener());
log.info("End to check prepare message, consumed time:{}", System.currentTimeMillis() - begin);
}
即最終執行的是:org.apache.rocketmq.broker.transaction.queue.TransactionalMessageServiceImpl#check 方法
該方法會不斷的通過固定的topic MixAll.RMQ_SYS_TRANS_HALF_TOPIC 組裝對應的 MessageQueue集合(根據該topic對應的配置 topicConfig.getReadQueueNums | 其實我對rocketmq topic配置信息裏爲什麼把readQueueNum和writeQueueNum分開,而不使用同一個數字感到不解),然後做事務回邏輯;
下面是《RocketMQ技術內幕》作者給我的解答: