RocketMQ事务消息详解与实战:半消息/TransactionListener/TransactionMQProducer

RocketMQ事务消息详解与实战

背景介绍

在分布式系统中,事务一致性是一个核心挑战。传统数据库事务难以直接应用于跨服务的场景,而分布式消息队列通过异步解耦提供了解决方案。然而,普通消息无法保证生产者的事务操作(如数据库更新)与消息发送的原子性,可能导致数据不一致。例如,订单系统在创建订单后发送消息,若事务失败但消息已发送,下游系统会处理错误数据。

RocketMQ 的事务消息机制解决了这一问题,确保本地事务与消息发送的原子性。它通过"两阶段提交"思想,支持生产者在执行本地事务后决定消息的最终状态(提交或回滚),从而保证一致性。事务消息广泛应用于分布式系统,如电商订单、支付系统等场景。

RocketMQ 对事务消息的支持

RocketMQ 的事务消息基于以下设计:

  • 半消息(Half Message)

    • 生产者发送的消息首先作为"半消息"存储在 Broker 中,对消费者不可见。
    • Broker 等待生产者确认消息状态(提交或回滚)。
  • 事务状态检查

    • 如果生产者未及时确认,Broker 会定时回查生产者的事务状态,决定消息是提交(对消费者可见)还是回滚(丢弃)。
  • API 支持

    • RocketMQ 提供了 TransactionMQProducerTransactionListener 接口,用于发送事务消息并处理本地事务逻辑。
  • 存储与可靠性

    • 事务消息存储在 Broker 的专用队列中,记录事务状态日志,确保高可用性和一致性。

相较于普通消息,事务消息增加了半消息阶段和状态检查机制,引入了额外的网络交互和回查逻辑,但保证了分布式一致性。

开发中需要调用的 API

在 RocketMQ 中,开发事务消息主要涉及以下 API:

  • TransactionMQProducer

    • 用于发送事务消息,需配置事务监听器。
    • 示例:TransactionMQProducer producer = new TransactionMQProducer("groupName");
  • TransactionListener

    • 定义本地事务执行和状态回查逻辑,实现以下两个方法:

      • executeLocalTransaction:执行本地事务,返回事务状态(COMMIT, ROLLBACK, 或 UNKNOWN)。
      • checkLocalTransaction:Broker 回查时调用,返回事务最终状态。
    • 示例:

      typescript 复制代码
      public class TransactionListenerImpl implements TransactionListener {
          @Override
          public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
              // 执行本地事务(如数据库操作)
              return LocalTransactionState.COMMIT_MESSAGE; // 或 ROLLBACK_MESSAGE, UNKNOWN
          }
      
          @Override
          public LocalTransactionState checkLocalTransaction(MessageExt msg) {
              // 检查本地事务状态
              return LocalTransactionState.COMMIT_MESSAGE;
          }
      }
  • 发送事务消息

    • 使用 producer.sendMessageInTransaction(msg, arg) 发送事务消息,触发 executeLocalTransaction
  • 配置与启动

    • 设置 Broker 地址、事务监听器,并启动生产者。

    • 示例:

      ini 复制代码
      producer.setNamesrvAddr("localhost:9876");
      producer.setTransactionListener(new TransactionListenerImpl());
      producer.start();

Broker 的处理流程与差异

Broker 在处理事务消息时,与普通消息相比有显著差异:

  • 半消息存储

    • 收到事务消息后,Broker 存储为半消息,写入专门的事务消息队列(RMQ_SYS_TRANS_HALF_TOPIC),对消费者不可见。
  • 事务状态记录

    • Broker 维护事务状态日志,记录半消息的元数据(如事务 ID)。
  • 定时回查

    • Broker 定时(默认 60 秒)检查未确认的半消息,向生产者发送回查请求,调用 checkLocalTransaction 方法。
    • 根据返回状态(COMMIT 或 ROLLBACK),Broker 将消息转为普通消息(投递给消费者)或丢弃。
  • 性能开销

    • 事务消息增加了半消息存储、状态管理和回查的开销,吞吐量低于普通消息。
    • 但通过异步提交和批量处理,RocketMQ 优化了性能。
  • 与普通消息的差异

    • 普通消息直接存储并对消费者可见,无需状态确认。
    • 事务消息引入半消息和回查机制,确保事务一致性,适用于高一致性场景。
    • 事务消息需要额外的 Broker 配置(如启用 transactionCheckInterval)。

实战项目:订单系统事务消息

以下是一个电商订单系统的实战案例,展示如何使用 RocketMQ 事务消息确保订单创建与消息发送的一致性。

项目背景

订单服务在创建订单后,需发送消息通知库存服务扣减库存。若订单创建失败,消息不应发送。我们使用 RocketMQ 事务消息保证一致性。

代码实现

java 复制代码
package com.example.rocketmq;

import org.apache.rocketmq.client.producer.LocalTransactionState;
import org.apache.rocketmq.client.producer.TransactionListener;
import org.apache.rocketmq.client.producer.TransactionMQProducer;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageExt;

import java.util.HashMap;
import java.util.Map;

public class OrderTransactionProducer {
    private static final String NAMESRV_ADDR = "localhost:9876";
    private static final String PRODUCER_GROUP = "order_producer_group";
    private static final String TOPIC = "OrderTopic";

    // 模拟数据库
    private static Map<String, Order> db = new HashMap<>();

    public static void main(String[] args) throws Exception {
        // 初始化事务生产者
        TransactionMQProducer producer = new TransactionMQProducer(PRODUCER_GROUP);
        producer.setNamesrvAddr(NAMESRV_ADDR);
        producer.setTransactionListener(new TransactionListenerImpl());
        producer.start();

        // 模拟订单数据
        String orderId = "ORDER_001";
        Message msg = new Message(TOPIC, ("OrderCreated:" + orderId).getBytes());
        producer.sendMessageInTransaction(msg, orderId);

        // 保持程序运行
        Thread.sleep(60000);
        producer.shutdown();
    }

    static class TransactionListenerImpl implements TransactionListener {
        @Override
        public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
            String orderId = (String) arg;
            try {
                // 模拟创建订单
                Order order = new Order(orderId, "PENDING");
                db.put(orderId, order);
                System.out.println("Order created: " + orderId);

                // 假设事务成功
                return LocalTransactionState.COMMIT_MESSAGE;
            } catch (Exception e) {
                System.out.println("Order creation failed: " + orderId);
                return LocalTransactionState.ROLLBACK_MESSAGE;
            }
        }

        @Override
        public LocalTransactionState checkLocalTransaction(MessageExt msg) {
            String orderId = new String(msg.getBody()).split(":")[1];
            // 检查订单状态
            Order order = db.get(orderId);
            if (order != null && "PENDING".equals(order.getStatus())) {
                System.out.println("Check: Order exists, commit: " + orderId);
                return LocalTransactionState.COMMIT_MESSAGE;
            }
            System.out.println("Check: Order not found, rollback: " + orderId);
            return LocalTransactionState.ROLLBACK_MESSAGE;
        }
    }

    static class Order {
        private String id;
        private String status;

        public Order(String id, String status) {
            this.id = id;
            this.status = status;
        }

        public String getStatus() {
            return status;
        }
    }
}
相关推荐
郝同学的测开笔记10 分钟前
云原生探索系列(十五):Go 语言通道
后端·云原生·go
开心猴爷1 小时前
Flutter 开发系列(八):Flutter 项目的自动化测试与调试
后端
开心就好20251 小时前
将Flutter推向极限:你应该知道的44个性能提示
后端
00后程序员1 小时前
【Flutter】自动测试探索
后端
aiopencode1 小时前
Flutter快学快用24讲--09 单元测试:Flutter 应用单元测试,提升代码质量
后端
调试人生的显微镜1 小时前
Nativefier——可以把网页打包成exe的工具
后端
疯狂的程序猴1 小时前
android studio 运行flutter报错
后端
duan8471 小时前
CentOS 7 离线安装 Docker
后端
AronTing1 小时前
09-RocketMQ 深度解析:从原理到实战,构建可靠消息驱动微服务
后端·面试·架构
方块海绵1 小时前
RabbitMQ总结
后端