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;
        }
    }
}
相关推荐
明月与玄武1 小时前
Spring Boot中的拦截器!
java·spring boot·后端
菲兹园长2 小时前
SpringBoot统一功能处理
java·spring boot·后端
muxue1782 小时前
go语言封装、继承与多态:
开发语言·后端·golang
开心码农1号2 小时前
Go语言中 源文件开头的 // +build 注释的用法
开发语言·后端·golang
北极象2 小时前
Go主要里程碑版本及其新增特性
开发语言·后端·golang
lyrhhhhhhhh3 小时前
Spring框架(1)
java·后端·spring
喝养乐多长不高4 小时前
Spring Web MVC基础理论和使用
java·前端·后端·spring·mvc·springmvc
莫轻言舞4 小时前
SpringBoot整合PDF导出功能
spring boot·后端·pdf
玄武后端技术栈5 小时前
什么是死信队列?死信队列是如何导致的?
后端·rabbitmq·死信队列
老兵发新帖6 小时前
NestJS 框架深度解析
后端·node.js