1. 事务消息原理
RocketMQ 事务消息的实现原理基于两阶段提交和定时事务状态回查来决定消息最终是提交还是回滚。
2. 事务消息启动
TransactionListener transactionListener = new TransactionListenerImpl();
TransactionMQProducer producer = new TransactionMQProducer("trans_producer");
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);
producer.setTransactionListener(transactionListener);
producer.start();
2.1 事务生产者启动
调用 org.apache.rocketmq.client.producer.TransactionMQProducer#start
:首先初始化事务生产者的基础配置,然后调用 org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl#start
:完成生产者的启动。这里的逻辑和默认的生产者启动逻辑一样。
public void start() throws MQClientException {
// 初始化事务消息环境,设置执行事务消息监听的线程池
this.defaultMQProducerImpl.initTransactionEnv();
// 启动默认的 producer 流程
super.start();
}
public void initTransactionEnv() {
// 事务状态执行的线程池初始化
TransactionMQProducer producer = (TransactionMQProducer) this.defaultMQProducer;
if (producer.getExecutorService() != null) {
this.checkExecutor = producer.getExecutorService();
} else {
this.checkRequestQueue = new LinkedBlockingQueue<Runnable>(2000);
this.checkExecutor = new ThreadPoolExecutor(
1,
1,
1000 * 60,
TimeUnit.MILLISECONDS,
this.checkRequestQueue);
}
}
3 发送事务消息
org.apache.rocketmq.client.producer.TransactionMQProducer#sendMessageInTransaction
:是事务生产者的发送消息的接口,首先检查事件监听器是否注册,如果为空,则直接抛出遗产给,最终调用 org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl#sendMessageInTransaction
:默认同步发送
public TransactionSendResult sendMessageInTransaction(final Message msg, final Object arg) throws MQClientException {
if (null == this.transactionListener) {
throw new MQClientException("TransactionListener is null", null);
}
return this.defaultMQProducerImpl.sendMessageInTransaction(msg, transactionListener, arg);
}
3.1 事务消息发送流程
-
org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl#sendMessageInTransaction
:发送事务消息,首先检查事务执行器是否存在、然后检查消息是否合法,然后给属性添加消息:TRAN_MSG:true
,PGROUP:groupName
。// 如果事务执行器为空,则抛出异常 if (null == tranExecuter) { throw new MQClientException("tranExecutor is null", null); } // 检查消息 Validators.checkMessage(msg, this.defaultMQProducer); SendResult sendResult = null; // 设置 msg 属性,TRAN_MSG:true MessageAccessor.putProperty(msg, MessageConst.PROPERTY_TRANSACTION_PREPARED, "true"); // PGROUP:groupName MessageAccessor.putProperty(msg, MessageConst.PROPERTY_PRODUCER_GROUP, this.defaultMQProducer.getProducerGroup()); try {
-
然后调用
org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl#send)
:默认 producer 的发送消息逻辑,上文分析过,这里针对事务消息,主要就是设置消息的类型为Trans_Msg_Half
消息,然后调用org.apache.rocketmq.broker.transaction.queue.TransactionalMessageServiceImpl#prepareMessage
: broker 接收到消息后,处理 prepare 消息。其他逻辑与一般消息一样。// 事务消息处理 final int tranType = MessageSysFlag.getTransactionValue(msg.getSysFlag()); // 事务提交或者不操作 if (tranType == MessageSysFlag.TRANSACTION_NOT_TYPE || tranType == MessageSysFlag.TRANSACTION_COMMIT_TYPE) { // 延迟投递, 保证延迟级别最大为 maxDelayLevel if (msg.getDelayTimeLevel() > 0) { if (msg.getDelayTimeLevel() > this.defaultMessageStore.getScheduleMessageService().getMaxDelayLevel()) { msg.setDelayTimeLevel(this.defaultMessageStore.getScheduleMessageService().getMaxDelayLevel()); } // 存储消息时,延迟消息进入 Topic 为 SCHEDULE_TOPIC_XXXX topic = ScheduleMessageService.SCHEDULE_TOPIC; // 延迟级别与消息队列编号做固定映射:queueId = delayLevel - 1 queueId = ScheduleMessageService.delayLevel2QueueId(msg.getDelayTimeLevel()); // 备份真实的主题和消息队列id到属性中 MessageAccessor.putProperty(msg, MessageConst.PROPERTY_REAL_TOPIC, msg.getTopic()); MessageAccessor.putProperty(msg, MessageConst.PROPERTY_REAL_QUEUE_ID, String.valueOf(msg.getQueueId())); msg.setPropertiesString(MessageDecoder.messageProperties2String(msg.getProperties())); // 设置修改后的 topic 和 queueId,这里保证了同一消息级别的消息都会放到同一个 queue msg.setTopic(topic); msg.setQueueId(queueId); } }
-
得到上一步的消息发送结果后,执行对应的操作。
- 发送成功,设置消息的
transactionId
,然后执行org.apache.rocketmq.client.producer.TransactionListener#executeLocalTransaction
:事务监听器中的本地事务的逻辑。得到事务消息的本地事务状态,这里是为了保证本地事务与业务代码处于同一个事务,也就是发送方这边的事务一致性。 - 发送失败:则设置本次事务状态为
ROLLBACK_MESSAGE
。
// 初始化本地事务状态为 UNKNOW LocalTransactionState localTransactionState = LocalTransactionState.UNKNOW; Throwable localException = null; switch (sendResult.getSendStatus()) { case SEND_OK: { try { // 设置事务id if (sendResult.getTransactionId() != null) { msg.putUserProperty("__transactionId__", sendResult.getTransactionId()); } // 获取消息客户端的事务id String transactionId = msg.getProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX); if (null != transactionId && !"".equals(transactionId)) { msg.setTransactionId(transactionId); } // 2.如果消息发送成功,处理与消息关联的本地事务单元,客户端自己实现的事务消息监听 localTransactionState = tranExecuter.executeLocalTransaction(msg, arg); // 返回的本地事务状态如果不为 commit,那么都会进行回滚 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
:结束事务,根据上一步得到的事务状态执行提交、回滚或者暂时不处理操作。最后向 broker 发送结束事务请求,根据事务状态,提交或者回滚commitlog
文件进度和是否删除prepare
消息。public void endTransaction( final SendResult sendResult, final LocalTransactionState localTransactionState, final Throwable localException) throws RemotingException, MQBrokerException, InterruptedException, UnknownHostException { //1.根据sendResult找到Prepared消息 ,sendResult包含事务消息的ID //2.根据localTransaction更新消息的最终状态 final MessageId id; if (sendResult.getOffsetMsgId() != null) { id = MessageDecoder.decodeMessageId(sendResult.getOffsetMsgId()); } else { id = MessageDecoder.decodeMessageId(sendResult.getMsgId()); } String transactionId = sendResult.getTransactionId(); // 找到 broker 地址,用于发送消息请求 final String brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(sendResult.getMessageQueue().getBrokerName()); EndTransactionRequestHeader requestHeader = new EndTransactionRequestHeader(); requestHeader.setTransactionId(transactionId); // commilog 文件偏移量 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; } requestHeader.setProducerGroup(this.defaultMQProducer.getProducerGroup()); requestHeader.setTranStateTableOffset(sendResult.getQueueOffset()); requestHeader.setMsgId(sendResult.getMsgId()); String remark = localException != null ? ("executeLocalTransactionBranch exception: " + localException.toString()) : null; // 发送请求,单向发送 this.mQClientFactory.getMQClientAPIImpl().endTransactionOneway(brokerAddr, requestHeader, remark, this.defaultMQProducer.getSendMsgTimeout()); }
3 提交或回滚事务
事务的提交或者回滚,都是依赖于本地事务逻辑执行后返回的结果,当结果为 COMMIT_MESSAGE
, ROLLBACK_MESSAGE
或者是 UNKNOW
时,都会进行不同的处理。
3.1 事务处理
不管本地事务的结果是什么,客户端都会发送一个单向的请求给 master 节点的 broker。broker 接收到请求后,根据请求码,会转发给 EndTransactionProcessor
处理。调用 org.apache.rocketmq.broker.processor.EndTransactionProcessor#processRequest
处理事务结果。
-
检查 store 的角色是否为 SALVE 模式,如果是直接返回。
// 如果 store 为 slave 模式,那么直接返回 if (BrokerRole.SLAVE == brokerController.getMessageStoreConfig().getBrokerRole()) { response.setCode(ResponseCode.SLAVE_NOT_AVAILABLE); LOGGER.warn("Message store is slave mode, so end transaction is forbidden. "); return response; }
-
如果事务状态为
TRANSACTION_NOT_TYPE
,则直接返回 null,不做处理.switch (requestHeader.getCommitOrRollback()) { // 不做操作,直接返回 null case MessageSysFlag.TRANSACTION_NOT_TYPE: { LOGGER.warn("The producer[{}] end transaction in sending message, and it's pending status." + "RequestHeader: {} Remark: {}", RemotingHelper.parseChannelRemoteAddr(ctx.channel()), requestHeader.toString(), request.getRemark()); return null; } ......... ......... }
-
如果事务状态为
TRANSACTION_COMMIT_TYPE
:表示本地事务执行成功,首先从 commitlog 文件中根据偏移量获取 prepare 消息,如果获取 prepare 消息成功,检查 prepare 消息是否合法。最后利用 prepare 消息,还原原始的topic
、queueId
等信息构建消息,发送最终消息并且将 prepare 消息删除。if (MessageSysFlag.TRANSACTION_COMMIT_TYPE == requestHeader.getCommitOrRollback()) { // 从 commitlog 文件中根据偏移量获取 prepare 消息 result = this.brokerController.getTransactionalMessageService().commitMessage(requestHeader); // 获取 prepare 消息成功 if (result.getResponseCode() == ResponseCode.SUCCESS) { // 检查 prepare 消息是否合法 RemotingCommand res = checkPrepareMessage(result.getPrepareMessage(), requestHeader); if (res.getCode() == ResponseCode.SUCCESS) { // 为什么用 prepare 消息,因为 prepare 消息不会被消费,所以会一直存在,直到要发送最终消息时, // 利用 prepare 消息,重新使用原始的 topic、queueId 构建消息,发送消息。最后将 prepare 消息删除, 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()); // 发送最终消息,其实就是将之前修改后的消息还原后的消息 RemotingCommand sendResult = sendFinalMessage(msgInner); // 删除 prepare 消息,然后 if (sendResult.getCode() == ResponseCode.SUCCESS) { this.brokerController.getTransactionalMessageService().deletePrepareMessage(result.getPrepareMessage()); } return sendResult; } return res; } }
-
如果事务状态为
TRANSACTION_ROLLBACK_TYPE
:表示本地事务执行失败,首先从 commitlog 文件中根据偏移量获取 prepare 消息,如果获取 prepare 消息成功,检查 prepare 消息是否合法。最后直接将 prepare 消息删除。这里是保证第一阶段是原子性的。if (MessageSysFlag.TRANSACTION_ROLLBACK_TYPE == requestHeader.getCommitOrRollback()) { // 从 commitlog 文件中根据偏移量获取 prepare 消息 result = this.brokerController.getTransactionalMessageService().rollbackMessage(requestHeader); // 获取 prepare 消息成功 if (result.getResponseCode() == ResponseCode.SUCCESS) { // 检查 prepare 消息是否合法 RemotingCommand res = checkPrepareMessage(result.getPrepareMessage(), requestHeader); if (res.getCode() == ResponseCode.SUCCESS) { // 回滚操作则直接删除 prepare 消息。 this.brokerController.getTransactionalMessageService().deletePrepareMessage(result.getPrepareMessage()); } return res; } }
-
上面两种事务状态提到的删除,并不是真正的删除,调用
org.apache.rocketmq.broker.transaction.queue.TransactionalMessageServiceImpl#deletePrepareMessage
:将 prepare 消息存储到RMQ_SYS_TRANS_OP_HALF_TOPIC
主题中,表示该事务消息 (prepare状态的消息)已经处理过了,为未处理的事务进行事务回查提供查找依据。public boolean deletePrepareMessage(MessageExt msgExt) { if (this.transactionalMessageBridge.putOpMessage(msgExt, TransactionalMessageUtil.REMOVETAG)) { log.info("Transaction op message write successfully. messageId={}, queueId={} msgExt:{}", msgExt.getMsgId(), msgExt.getQueueId(), msgExt); return true; } else { log.error("Transaction op message write failed. messageId is {}, queueId is {}", msgExt.getMsgId(), msgExt.getQueueId()); return false; } }
3.2 事务回查
RocketMQ 通过
TransactionalMessageCheckService
线程 时去检测RMQ_SYS_ TRANS_ HALF TOPIC
主题中的消息,回查消息 事务状态TransactionalMessageCheckService
的检测频率默认为 1 分钟,可通过在broker.conf
文件中设置transactionChecklnterval
来改变默认值,单位为毫秒。-
这里事务状态为
TRANSACTION_COMMIT_TYPE
、TRANSACTION_ROLLBACK_TYPE
的逻辑都已经处理完毕,当状态为TRANSACTION_NOT_TYPE
,prepare 消息还没有删除,那么就会交给事务回查服务TransactionalMessageCheckService
去回查事务状态,也就是自定义的事务回查接口。这个服务是org.apache.rocketmq.broker.BrokerController#initialTransaction
broker控制器初始化时,启动的事务回查服务。public void start() { // started 默认为 false if (started.compareAndSet(false, true)) { // 运行 run 方法 super.start(); this.brokerController.getTransactionalMessageService().open(); } } public void run() { log.info("Start transaction check service thread!"); // 默认检查间隔为 60 * 1000 = 1s long checkInterval = brokerController.getBrokerConfig().getTransactionCheckInterval(); while (!this.isStopped()) { // 执行 this.waitForRunning(checkInterval); } log.info("End transaction check service thread!"); }
-
org.apache.rocketmq.broker.transaction.TransactionalMessageCheckService#onWaitEnd
: 回查消息的事务状态。
protected void onWaitEnd() {
// 事务过期时间,默认为 3s
long timeout = brokerController.getBrokerConfig().getTransactionTimeOut();
// 事务回查最大检查次数,默认为 5次
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);
}3. `org.apache.rocketmq.broker.transaction.queue.TransactionalMessageServiceImpl#check`:核心方法,检查消息的事务状态,通过主题 `RMQ_SYS_TRANS_HALF_TOPIC` 获取消息队列,即 prepare 消息所在队列。 ```java // 处理 prepare 消息的主题 String topic = MixAll.RMQ_SYS_TRANS_HALF_TOPIC; // 从 broker 中获取 topic 的路由信息和消息队列 Set<MessageQueue> msgQueues = transactionalMessageBridge.fetchMessageQueues(topic); if (msgQueues == null || msgQueues.size() == 0) { log.warn("The queue of topic is empty :" + topic); return; }
-
遍历获取到的消息队列,获取已经处理过的 prepare 消息,主题为
SYS_TRANS_OP_HALF_TOPIC
,即一阶段是提交的事务状态为提交、回滚状态的消息。然后分别获取同一消息队列上 prepare 消息进度、已经处理过的 prepare 消息进度。for (MessageQueue messageQueue : msgQueues) { long startTime = System.currentTimeMillis(); // 获取处理过的 prepare 消息的消息主题 RMQ SYS_TRANS_OP_HALF_TOPIC MessageQueue opQueue = getOpQueue(messageQueue); // 获取 prepare 消息的消费进度 long halfOffset = transactionalMessageBridge.fetchConsumeOffset(messageQueue); // 获取处理过的 prepare 消息的消息进度 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; } ....... ....... }
-
调用
org.apache.rocketmq.broker.transaction.queue.TransactionalMessageServiceImpl#fillOpRemoveMap
: 从opQueue
中拉取 32 条消息,方便判断消息是否已经处理过,如果处理过,就不用再次发送事务状态回查请求,避免重复的事务回查。List<Long> doneOpOffset = new ArrayList<>(); HashMap<Long, Long> removeMap = new HashMap<>(); // 根据当前的处理进度依次从已处理队列拉取 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; } private PullResult fillOpRemoveMap(HashMap<Long, Long> removeMap, MessageQueue opQueue, long pullOffsetOfOp, long miniOffset, List<Long> doneOpOffset) { // 从 opQueue 中拉取 32条 消息 PullResult pullResult = pullOpMsg(opQueue, pullOffsetOfOp, 32); if (null == pullResult) { return null; } // 拉取消息状态 if (pullResult.getPullStatus() == PullStatus.OFFSET_ILLEGAL || pullResult.getPullStatus() == PullStatus.NO_MATCHED_MSG) { log.warn("The miss op offset={} in queue={} is illegal, pullResult={}", pullOffsetOfOp, opQueue, pullResult); transactionalMessageBridge.updateConsumeOffset(opQueue, pullResult.getNextBeginOffset()); return pullResult; } else if (pullResult.getPullStatus() == PullStatus.NO_NEW_MSG) { log.warn("The miss op offset={} in queue={} is NO_NEW_MSG, pullResult={}", pullOffsetOfOp, opQueue, pullResult); return pullResult; } List<MessageExt> opMsg = pullResult.getMsgFoundList(); if (opMsg == null) { log.warn("The miss op offset={} in queue={} is empty, pullResult={}", pullOffsetOfOp, opQueue, pullResult); return pullResult; } // 拉取已经处理的 prepare 消息成功 for (MessageExt opMessageExt : opMsg) { Long queueOffset = getLong(new String(opMessageExt.getBody(), TransactionalMessageUtil.charset)); log.info("Topic: {} tags: {}, OpOffset: {}, HalfOffset: {}", opMessageExt.getTopic(), opMessageExt.getTags(), opMessageExt.getQueueOffset(), queueOffset); if (TransactionalMessageUtil.REMOVETAG.equals(opMessageExt.getTags())) { if (queueOffset < miniOffset) { // 已经处理过的消息集合 doneOpOffset.add(opMessageExt.getQueueOffset()); } else { // 将要移除的消息集合 key:halfOffset, value: opOffset. removeMap.put(queueOffset, opMessageExt.getQueueOffset()); } } else { log.error("Found a illegal tag in opMessageExt= {} ", opMessageExt); } } // 需要移除的 op 消息进度 log.debug("Remove map: {}", removeMap); // 已经完成的消息进度 log.debug("Done op list: {}", doneOpOffset); return pullResult; }
-
接下来处理
pullResult
结果如果 prepare 消息的偏移量已经处理过了,那么则从集合中移除,执行下一次操作。// 获取空消息的次数 int getMessageNullCount = 1; // 当前处理 RMQ_SYS_TRANS_HALF_TOPIC#queueld 的最新进度 long newOffset = halfOffset; // 当前处理消息的队列偏移量,主题依然为 RMQ_SYS_TRANS_HALF_TOPIC long i = halfOffset; while (true) { // 处理时间大于 1min 则停止 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 { ..... ..... } newOffset = i + 1; i++; }
-
当 prepare 消息的偏移量没有处理过,那么根据偏移量获取 prepare 消息,检查消息是否合法。
// 根据消息队列偏移量 从消费队列中获取消息 GetResult getResult = getHalfMsg(messageQueue, i); MessageExt msgExt = getResult.getMsg(); // 消息为空 if (msgExt == null) { // 当拉取消息为空时,默认重试 1次,超过则结束该消息队列的事务状态回查 if (getMessageNullCount++ > MAX_RETRY_COUNT_WHEN_HALF_NULL) { break; } // 没有新消息,直接 break if (getResult.getPullResult().getPullStatus() == PullStatus.NO_NEW_MSG) { log.info("No new msg, the miss offset={} in={}, continue check={}, pull result={}", i, messageQueue, getMessageNullCount, getResult.getPullResult()); break; } else { // i 设置为下一个偏移量,重新拉取 log.info("Illegal offset, the miss offset={} in={}, continue check={}, pull result={}", i, messageQueue, getMessageNullCount, getResult.getPullResult()); i = getResult.getPullResult().getNextBeginOffset(); newOffset = i; continue; } }
-
走到这里表示消息是经过检查了的,判断消息是否需要丢弃、跳过。丢弃标准为:消息回查的次数大于允许的最大回查次数,则丢弃,消息的属性TRANSACTION_CHECK_TIMES 加1,默认为 5,跳过标准:事务消息超过文件的过期时间,默认为 72 小时,如果确定丢弃或者跳过,则执行下一次获取的消息。继续判断消息的存储时间是否大于当前时间,如果大于,表示该消息是在回查之后添加的消息,直接 break。
// 消息不为空时,判断消息是否需要丢弃(吞没、丢弃、不处理)或者跳过 // 判断丢弃:消息回查的次数大于允许的最大回查次数,则丢弃,消息的属性TRANSACTION_CHECK_TIMES 加1,默认为 5 // 判断跳过:事务消息超过文件的过期时间,默认为 72 小时 if (needDiscard(msgExt, transactionCheckMax) || needSkip(msgExt)) { listener.resolveDiscardMsg(msgExt); newOffset = i + 1; i++; continue; } // 存储时间 >= 开始时间,表示之后添加进来的消息,这里不做处理,break if (msgExt.getStoreTimestamp() >= startTime) { log.info("Fresh stored. the miss offset={}, check it later, store={}", i, new Date(msgExt.getStoreTimestamp())); break; }
-
检查当前消息是否能够进行执行回查请求,判断消息的存储时间和最开始 check 方法时间、存储时间和立即检测消息等。
// 消息已存储的时间,为系统当前时间减去消息存储的时间戳 long valueOfCurrentMinusBorn = System.currentTimeMillis() - msgExt.getBornTimestamp(); // checkImmunityTime:立即检测事务消息的时间,transactionTimeout:事务消息的超时时间,默认为 3s long checkImmunityTime = transactionTimeout; // 消息事务消息回查请求的最晚时间,只有在这个时间内收到回查消息才有效,默认为 null String checkImmunityTimeStr = msgExt.getUserProperty(MessageConst.PROPERTY_CHECK_IMMUNITY_TIME_IN_SECONDS); if (null != checkImmunityTimeStr) { checkImmunityTime = getImmunityTime(checkImmunityTimeStr, transactionTimeout); //消息已存储的时间 < 立即检查的时间,等待下一次再试 if (valueOfCurrentMinusBorn < checkImmunityTime) { // 如果返回 true 则跳过该消息 if (checkPrepareQueueOffset(removeMap, doneOpOffset, msgExt, checkImmunityTime)) { newOffset = i + 1; i++; continue; } } } else { // checkImmunityTime=transactionTimeout,新到达的消息不在本次范围,直接break if ((0 <= valueOfCurrentMinusBorn) && (valueOfCurrentMinusBorn < checkImmunityTime)) { log.info("New arrived, the miss offset={}, check it later checkImmunity={}, born={}", i, checkImmunityTime, new Date(msgExt.getBornTimestamp())); break; } }
-
走到这里,表示最后的检查,是否需要发送事务回查消息,确定执行回查请求后,调用异步线程池发送事务回查请求。
- 如果已处理消息队列中没有已处理消息并且已经超过应用程序事务结束时间,即 transactionTimeout
- 已处理消息队列不为空,并且最后一条消息的存储时间大于 transactionTimeout。
// 判断是否需要发送事务回查消息: // 1. 如果已处理消息队列中没有已处理消息并且已经超过应用程序事务结束时间,即 transactionTimeout // 2. 已处理消息队列不为空,并且最后一条消息的存储时间大于 transactionTimeout。 boolean isNeedCheck = (opMsg == null && valueOfCurrentMinusBorn > checkImmunityTime) || (opMsg != null && (opMsg.get(opMsg.size() - 1).getBornTimestamp() - startTime > transactionTimeout)) || (valueOfCurrentMinusBorn <= -1); if (isNeedCheck) { // 先将消息放入 RMQ_SYS_TRANS_HALF_TOPIC 主题,发送成功为 true if (!putBackHalfMsgQueue(msgExt, i)) { continue; } // 调用异步线程池发送事务回查命令 listener.resolveHalfMsg(msgExt); } else { // 再次获取已处理消息进度,用于后续的过滤重复处理数据 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; }
-
最后更新 prepare 消息消费队列、已处理过 prepare 消息队列。
// 表示存在多条消息,更新消费队列 if (newOffset != halfOffset) { transactionalMessageBridge.updateConsumeOffset(messageQueue, newOffset); } // 重新计算已经处理过的消息的消费进度 long newOpOffset = calculateOpOffset(doneOpOffset, opOffset); // 表示增加了已处理的消息进度,更新消费队列 if (newOpOffset != opOffset) { transactionalMessageBridge.updateConsumeOffset(opQueue, newOpOffset); }
-
3.3 发送事务回查
org.apache.rocketmq.broker.transaction.AbstractTransactionalMessageCheckListener#resolveHalfMsg
:异步线程池发送回查请求, org.apache.rocketmq.broker.transaction.AbstractTransactionalMessageCheckListener#sendCheckMessage
:发送回查请求。
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);
// 获取生产组id
String groupId = msgExt.getProperty(MessageConst.PROPERTY_PRODUCER_GROUP);
// 根据生产者组id,获取可用的生产者
Channel channel = brokerController.getProducerManager().getAvaliableChannel(groupId);
if (channel != null) {
// 处理事务回查,最终会调用生产者自己定义的方法 org.apache.rocketmq.client.producer.TransactionListener.checkLocalTransaction
// 根据提交的事务结果,再次进行一阶段的事务处理,提交、回滚或者不做处理。
brokerController.getBroker2Client().checkProducerTransactionState(groupId, channel, checkTransactionStateRequestHeader, msgExt);
} else {
LOGGER.warn("Check transaction failed, channel is null. groupId={}", groupId);
}
}