事务消息

原文:juejin.cn/post/684490...

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,代表该消息已被处理

作者:炳臣

链接:juejin.cn/post/684490...

来源:稀土掘金

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

半消息事务回查

两段式协议发送与提交回滚消息,执行完本地事务消息的状态为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() 方法内部逻辑)
  1. 扫描半消息存储 :从专用主题 RMQ_SYS_TRANS_HALF_TOPIC 中读取所有未确认的半消息。
  2. 筛选超时消息 :判断半消息的存储时间是否超过 timeout,只处理超时未确认的消息。
  3. 检查回查次数 :如果某条消息的回查次数已达 checkMax,则直接标记为回滚并清理。
  4. 发起回查请求 :对未超时且未达最大回查次数的消息,通过 TransactionalMessageCheckListener 向对应的 Producer 发送回查请求,询问本地事务的最终状态。
  5. 处理回查结果:根据 Producer 返回的状态(提交 / 回滚),执行对应的提交或回滚逻辑(同之前的 "二次提交" 流程)。
相关推荐
BingoGo几秒前
PHP 集成 FFmpeg 处理音视频处理完整指南
后端·php
数字人直播7 分钟前
稳了!青否数字人分享3大精细化AI直播搭建方案!
前端·后端
掘金一周19 分钟前
被老板逼出来的“表格生成器”:一个前端的自救之路| 掘金一周 8.21
前端·人工智能·后端
SimonKing39 分钟前
开源新锐:SQL玩转搜索引擎?Manticore颠覆你的认知
java·后端·程序员
MaxHua2 小时前
数据库入门指南与实战进阶-Mysql篇
后端
用户4099322502122 小时前
FastAPI的死信队列处理机制:为何你的消息系统需要它?
后端·ai编程·trae
用户4822137167752 小时前
C++——纯虚函数、抽象类
后端
张同学的IT技术日记2 小时前
必看!用示例代码学 C++ 基础入门,快速掌握基础知识,高效提升编程能力
后端
林太白2 小时前
Nuxt3 功能篇
前端·javascript·后端
得物技术3 小时前
营销会场预览直通车实践|得物技术
后端·架构·测试