文章基于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技术内幕》作者给我的解答: