Rocketmq分布式事物消息

本文解析了RocketMQ中分布式事务消息的实现原理,重点介绍了TransactionMQProducer类及sendMessageInTransaction方法的工作流程,并分析了endTransaction方法如何确认事务状态。

Rocketmq源码中关于分布式事物消息的实现并没有完全开源,本人是基于3.4.6分析。其中的类是TransactionMQProducer。

源码分析
  1. TransactionMQProducer是发送分布式事物消息的核心基础类,其中sendMessageInTransaction是主要发送方法。该方法的核心步骤主要有3步。
    1.1. producer发送一条prepared消息至broker.
    1.2. 调用LocalTransactionExecutor执行本地业务逻辑,提交事物。
    1.3. 本地事物提交之后,发送事物确认消息。
  2. 分析一下sendMessageInTransaction源码
 public TransactionSendResult sendMessageInTransaction(final Message msg,final LocalTransactionExecuter tranExecuter, final Object arg) throws MQClientException {
       // 1. tranExecutor,需要应用自己开发实现
        if (null == tranExecuter) {
            throw new MQClientException("tranExecutor is null", null);
        }
      // 2. 检查消息各种属性和装填
        Validators.checkMessage(msg, this.defaultMQProducer);
        SendResult sendResult = null;
       // 3.1 设置发送消息属性类型,事物预备消息 
        MessageAccessor.putProperty(msg, MessageConst.PROPERTY_TRANSACTION_PREPARED, "true");
       // 3.2 设置消息所属的group
       MessageAccessor.putProperty(msg, MessageConst.PROPERTY_PRODUCER_GROUP,
            this.defaultMQProducer.getProducerGroup());
        try {
        // 4. 发送预备消息
            sendResult = this.send(msg);
        }
        catch (Exception e) {
            throw new MQClientException("send message Exception", e);
        }
        // 5. 初始化本地事物装填--UNKNOW未知状态
        LocalTransactionState localTransactionState = LocalTransactionState.UNKNOW;
        Throwable localException = null;
        // 6. 判断发送预备消息的状态
        switch (sendResult.getSendStatus()) {
        case SEND_OK: {
        try {
            if (sendResult.getTransactionId() != null) {        msg.putUserProperty("__transactionId__",sendResult.getTransactionId());
             }
        // 7. 执行本地事物逻辑,用户自己实现的业务逻辑部分
        localTransactionState = tranExecuter.executeLocalTransactionBranch(msg, arg);
        if (null == localTransactionState) {
            localTransactionState = LocalTransactionState.UNKNOW;
          }
        // 8.判断本地事物状态    
        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;
        }
    // 9 根据本地事物状态,发送事物确认消息。
        try {
            this.endTransaction(sendResult, localTransactionState, localException);
        }
        catch (Exception e) {
            log.warn("local transaction execute " + localTransactionState
                    + ", but end broker transaction failed", e);
        }
    // 1. 构建返回发送事物结果
        TransactionSendResult transactionSendResult = new TransactionSendResult();
        transactionSendResult.setSendStatus(sendResult.getSendStatus());
        transactionSendResult.setMessageQueue(sendResult.getMessageQueue());
        transactionSendResult.setMsgId(sendResult.getMsgId());
        transactionSendResult.setQueueOffset(sendResult.getQueueOffset());
        transactionSendResult.setTransactionId(sendResult.getTransactionId());
        transactionSendResult.setLocalTransactionState(localTransactionState);
        return transactionSendResult;
    }
  1. 分析一下endTransaction源码
 private void endTransaction(//
            final SendResult sendResult, //
            final LocalTransactionState localTransactionState, //
            final Throwable localException) throws RemotingException, MQBrokerException,InterruptedException, UnknownHostException {
        // 消息ID
        final MessageId id = MessageDecoder.decodeMessageId(sendResult.getMsgId());
        // 事物ID
        String transactionId = sendResult.getTransactionId();
        // 获取brokerAddr
        final String brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(sendResult.getMessageQueue().getBrokerName());
       // 构建确认事物请求头信息
        EndTransactionRequestHeader requestHeader = new EndTransactionRequestHeader();
        // 设置事物请求ID
        requestHeader.setTransactionId(transactionId);
        // 设置消息的偏移量offset
        requestHeader.setCommitLogOffset(id.getOffset());
        // 判断本地事物状态
        switch (localTransactionState) {
        case COMMIT_MESSAGE:
    /**设置成功消息类型**/       requestHeader.setCommitOrRollback(MessageSysFlag.TransactionCommitType);
            break;
        case ROLLBACK_MESSAGE:
 /**设置回滚消息类型**/            requestHeader.setCommitOrRollback(MessageSysFlag.TransactionRollbackType);
            break;
        case UNKNOW:
            requestHeader.setCommitOrRollback(MessageSysFlag.TransactionNotType);
            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());
    }
### MySQL 分布式事务结合 RocketMQ 的实现方案 #### 背景介绍 分布式系统的普及使得传统的单体应用逐渐向微服务架构转变。在这种背景下,分布式事务成为了一个重要的技术挑战[^4]。为了应对这一需求,许多企业采用了消息队列中间件(如 RocketMQ)来解决跨服务的数据一致性问题。 #### 方案概述 RocketMQ 提供了一种基于两阶段提交协议的分布式事务解决方案[^5]。该方案的核心思想是在生产者发送消息的第一阶段预处理数据,在第二阶段确认最终状态。以下是具体的实现细节: --- #### 技术原理 1. **第一阶段:准备阶段** 生产者发起事务请求时,首先执行本地数据库的操作,并将操作的结果记录到日志中。随后,生产者向 RocketMQ 发送一条半消息(Half Message),表示此消息处于待确认状态[^2]。 2. **第二阶段:提交/回滚** - 如果本地事务成功,则生产者显式调用 `commit` 方法通知 RocketMQ 将半消息转为正式消息并投递给消费者。 - 如果本地事务失败,则生产者调用 `rollback` 方法取消该消息。 - 若生产者未及时响应,RocketMQ 会触发回调机制询问生产者的事务状态,并据此决定是否提交或回滚。 3. **补偿机制** 在某些情况下,如果生产者宕机或者网络异常导致无法正常完成二阶段操作,RocketMQ 支持定时扫描未决事务列表并向生产者查询其实际状态。这一步骤能够有效防止消息丢失或重复消费[^3]。 --- #### 实现步骤 下面是一个简单的代码示例展示如何在 Java 中集成 MySQL 和 RocketMQ 来管理分布式事务: ```java // 定义事务监听器类 public class TransactionListenerImpl implements TransactionListener { @Override public LocalTransactionState executeLocalTransaction(Message msg, Object arg) { try { // 执行本地数据库更新逻辑 updateDatabase(); // 返回临时状态等待后续确认 return LocalTransactionState.UNKNOW; } catch (Exception e) { // 出错则直接回滚 return LocalTransactionState.ROLLBACK_MESSAGE; } } @Override public LocalTransactionState checkLocalTransaction(MessageExt msg) { // 查询本地事务状态以决定是提交还是回滚 boolean isCommitted = queryTransactionStatusFromDB(msg); if (isCommitted) { return LocalTransactionState.COMMIT_MESSAGE; } else { return LocalTransactionState.ROLLBACK_MESSAGE; } } private void updateDatabase() throws SQLException { Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", ""); String sql = "INSERT INTO orders(order_id, product_name) VALUES (?, ?)"; PreparedStatement pstmt = conn.prepareStatement(sql); pstmt.setInt(1, 101); pstmt.setString(2, "Product A"); pstmt.executeUpdate(); } private boolean queryTransactionStatusFromDB(MessageExt msg) { // 假设这里通过某种方式判断事务是否已提交 return true; // 示例返回true } } ``` 上述代码展示了如何定义一个自定义的事务监听器以及如何在其内部封装本地数据库交互过程。 --- #### 最佳实践建议 1. **减少业务复杂度** 需要特别注意的是,尽管有强大的工具支持,但设计良好的分布式事务仍然依赖于清晰简洁的业务流程。复杂的多步操作往往增加出错概率,应尽量将其分解成更小粒度的任务单元[^1]。 2. **合理配置超时时间** 设置合理的最大等待时间和重试策略对于保障系统稳定性至关重要。过短的时间可能导致不必要的失败判定;而过长又会影响整体吞吐量。 3. **监控与报警** 对整个链路进行全面实时监测可以帮助快速定位潜在风险点。例如可以借助 Prometheus + Grafana 构建可视化面板跟踪各环节耗时情况及错误率变化趋势。 --- #### 总结 综上所述,采用 RocketMQ 处理涉及 MySQL 数据库变更的分布式事务是一种高效可行的方法。它不仅具备高性能特点还能很好地兼容现有基础设施环境。当然具体实施方案还需根据项目实际情况灵活调整优化参数设置等内容。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值