

Broker处理事务消息的二次提交
接下来我们来一起看看,当Producer或者回查定时任务提交/回滚事务的时候,Broker如何处理事务消息提交、回滚命令的。
1. 前置处理:请求解析与角色校验
ini
final EndTransactionRequestHeader requestHeader = (EndTransactionRequestHeader)request.decodeCommandCustomHeader(EndTransactionRequestHeader.class);
if (BrokerRole.SLAVE == brokerController.getMessageStoreConfig().getBrokerRole()) {
response.setCode(ResponseCode.SLAVE_NOT_AVAILABLE);
return response;
}
- 请求头解析 :
EndTransactionRequestHeader
包含事务状态关键信息,如commitOrRollback
(标记是提交还是回滚)、commitLogOffset
(半消息在 Broker 存储中的偏移量,用于定位半消息)等。 - 主从角色校验:只有主 Broker 处理事务状态确认请求,从 Broker 直接返回 "不可用",避免分布式环境下的重复处理。
2. 事务提交处理(核心流程)
当 requestHeader.getCommitOrRollback()
为 MessageSysFlag.TRANSACTION_COMMIT_TYPE
时,执行提交逻辑:
(1)定位半消息
ini
result = this.brokerController.getTransactionalMessageService().commitMessage(requestHeader);
- 通过请求头中的
commitLogOffset
(半消息在 CommitLog 中的存储位置),从 Broker 存储中找到之前存储的半消息(result.getPrepareMessage()
)。
(2)校验半消息合法性
ini
RemotingCommand res = checkPrepareMessage(result.getPrepareMessage(), requestHeader);
- 检查找到的半消息是否与请求匹配(如事务 ID、生产者组等),避免处理非法或过期的事务请求。
(3)恢复消息的真实主题和队列
ini
MessageExtBrokerInner msgInner = endMessageTransaction(result.getPrepareMessage());
-
半消息之前被存储在专用主题(
RMQ_SYS_TRANS_HALF_TOPIC
),这里需要从半消息的属性中恢复其原始信息:- 从
PROPERTY_REAL_TOPIC
恢复用户指定的原主题(如order_topic
)。 - 从
PROPERTY_REAL_QUEUE_ID
恢复原队列 ID。
- 从
-
目的是让消息最终能被投递到消费者订阅的真实主题。
(4)清除事务标记,标记为可消费
less
msgInner.setSysFlag(MessageSysFlag.resetTransactionValue(msgInner.getSysFlag(), requestHeader.getCommitOrRollback()));
MessageAccessor.clearProperty(msgInner, MessageConst.PROPERTY_TRANSACTION_PREPARED);
- 清除半消息的事务标记(
PROPERTY_TRANSACTION_PREPARED
),并更新系统标记为 "已提交",表示这是一条可正常消费的消息。
(5)发送最终消息到原主题
ini
RemotingCommand sendResult = sendFinalMessage(msgInner);
- 将恢复后的消息存储到原主题的队列中,此时消费者可以正常订阅并消费这条消息(完成事务的最终提交)。
(6)"删除" 半消息(标记为已处理)
kotlin
this.brokerController.getTransactionalMessageService().deletePrepareMessage(result.getPrepareMessage());
- 注意:这里的 "删除" 并非物理删除,而是将半消息从
RMQ_SYS_TRANS_HALF_TOPIC
移动到 Broker 内部的另一个专用主题RMQ_SYS_TRANS_OP_HALF_TOPIC
,标记为 "已处理"。 - 目的是避免 Broker 对已确认的半消息进行重复回查,同时保留操作记录用于后续审计或故障排查。
3. 事务回滚处理
当 requestHeader.getCommitOrRollback()
为 MessageSysFlag.TRANSACTION_ROLLBACK_TYPE
时,执行回滚逻辑:
ini
result = this.brokerController.getTransactionalMessageService().rollbackMessage(requestHeader);
if (result.getResponseCode() == ResponseCode.SUCCESS) {
RemotingCommand res = checkPrepareMessage(result.getPrepareMessage(), requestHeader);
if (res.getCode() == ResponseCode.SUCCESS) {
this.brokerController.getTransactionalMessageService().deletePrepareMessage(result.getPrepareMessage());
}
return res;
}
- 回滚逻辑相对简单:通过
commitLogOffset
定位半消息后,直接执行 "删除" 操作(同样是移动到RMQ_SYS_TRANS_OP_HALF_TOPIC
)。 - 由于半消息从未投递到原主题,回滚后消费者不会感知到这条消息,相当于事务从未发生。
其核心实现如下:
- 根据commitlogOffset找到消息
- 如果是提交动作,就恢复原消息的主题与队列,再次存入commitlog文件进而转到消息消费队列,供消费者消费,然后将原预处理消息存入一个新的主题RMQ_SYS_TRANS_OP_HALF_TOPIC,代表该消息已被处理
- 回滚消息,则直接将原预处理消息存入一个新的主题RMQ_SYS_TRANS_OP_HALF_TOPIC,代表该消息已被处理
作者:炳臣
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
半消息事务回查

两段式协议发送与提交回滚消息,执行完本地事务消息的状态为UNKNOW
时,结束事务不做任何操作。通过事务状态定时回查得到发送端的事务状态是rollback或commit。
通过TransactionalMessageCheckService
线程定时去检测RMQ_SYS_TRANS_HALF_TOPIC
主题中的消息,回查消息的事务状态。
- RMQ_SYS_TRANS_HALF_TOPIC
prepare消息的主题,事务消息首先先进入到该主题。 - RMQ_SYS_TRANS_OP_HALF_TOPIC
当消息服务器收到事务消息的提交或回滚请求后,会将消息存储在该主题下。
代码入口:
java
public void run() {
log.info("Start transaction check service thread!");
//执行间隔
long checkInterval = brokerController.getBrokerConfig().getTransactionCheckInterval();
while (!this.isStopped()) {
this.waitForRunning(checkInterval);
}
log.info("End transaction check service thread!");
}
@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);
}
事务消息可能因为以下原因处于 "未知状态"(UNKNOW
):
- Producer 发送半消息成功,但执行本地事务时发生异常(如网络中断),未及时向 Broker 上报状态。
- Producer 上报状态的请求在网络传输中丢失,Broker 未收到最终的提交 / 回滚指令。 此时,Broker 需要主动发起回查,通过这段代码实现的定时任务来解决这个问题。
执行半消息检查逻辑
java
@Override
protected void onWaitEnd() {
// 事务超时时间(默认 60 秒,可通过消息属性覆盖)
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);
}
(1)关键参数
timeout
:半消息的超时时间。如果半消息超过此时长仍未收到确认(提交 / 回滚),则触发回查。checkMax
:最大回查次数。如果超过此次数仍未得到明确状态,Broker 会默认将其标记为回滚(避免资源长期占用)。
(2)核心检查流程(check()
方法内部逻辑)
- 扫描半消息存储 :从专用主题
RMQ_SYS_TRANS_HALF_TOPIC
中读取所有未确认的半消息。 - 筛选超时消息 :判断半消息的存储时间是否超过
timeout
,只处理超时未确认的消息。 - 检查回查次数 :如果某条消息的回查次数已达
checkMax
,则直接标记为回滚并清理。 - 发起回查请求 :对未超时且未达最大回查次数的消息,通过
TransactionalMessageCheckListener
向对应的 Producer 发送回查请求,询问本地事务的最终状态。 - 处理回查结果:根据 Producer 返回的状态(提交 / 回滚),执行对应的提交或回滚逻辑(同之前的 "二次提交" 流程)。