RocketMQ 事务消息实现及核心使用场景(完整实战指南)

RocketMQ 事务消息是解决分布式事务的核心方案,基于「两阶段提交 + 消息回查」机制,保证「本地事务」与「消息发送」的最终一致性。

一、事务消息核心原理(先理解再上手)

1. 分布式事务痛点

在跨服务/跨库场景中(如「下单扣库存」),传统方式易出现:

  • 本地事务提交成功,但消息发送失败 → 下游服务未执行;
  • 消息发送成功,但本地事务回滚 → 下游服务执行了无效操作。

2. RocketMQ 事务消息流程(两阶段+回查)

RocketMQ 事务消息通过「半消息(Half Message)+ 本地事务 + 提交/回滚 + 回查」解决一致性问题,核心流程:
消费者 回查服务 生产者 回查服务(CheckListener) 消费者(Consumer) RocketMQ Broker 生产者(Producer) 消费者 回查服务 生产者 回查服务(CheckListener) 消费者(Consumer) RocketMQ Broker 生产者(Producer) alt [本地事务成功] [本地事务失败] [本地事务结果未知(超时/异常)] 1. 发送半消息(暂不投递) 半消息发送成功 2. 执行本地事务(如订单入库) 3. 提交消息(Broker标记为可投递) 3. 回滚消息(Broker删除半消息) 4. 定时回查本地事务状态 5. 返回事务结果(提交/回滚) 6. 仅提交的消息会被投递到消费者 7. 消费确认(ACK)

3. 核心概念

术语 作用
半消息 发送到 Broker 但暂不投递的消息,仅生产者可确认其最终状态(提交/回滚)
本地事务 生产者端的数据库操作(如订单入库、库存扣减)
事务监听器 生产者端实现,包含「执行本地事务」和「回查本地事务状态」两个核心方法
消息回查 Broker 对超时未确认的半消息,主动查询生产者本地事务状态

二、完整代码实现(Spring Boot + RocketMQ 事务消息)

前置准备

  1. 安装 RocketMQ 并启动 NameServer、Broker(需开启事务消息支持,默认开启);
  2. Spring Boot 项目引入依赖:
xml 复制代码
<!-- RocketMQ Spring Boot Starter(适配 4.x/5.x) -->
<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-spring-boot-starter</artifactId>
    <version>2.2.3</version>
</dependency>
  1. 配置 application.yml
yaml 复制代码
rocketmq:
  name-server: 127.0.0.1:9876 # NameServer 地址
  producer:
    group: order-transaction-group # 事务生产者组(必须指定)
    send-message-timeout: 30000 # 发送超时时间
    retry-times-when-send-failed: 2 # 发送失败重试次数

步骤1:定义事务消息实体

java 复制代码
import lombok.Data;

/**
 * 订单事务消息实体
 */
@Data
public class OrderTransactionMessage {
    private Long orderId; // 订单ID
    private Long productId; // 商品ID
    private Integer num; // 购买数量
    private String orderNo; // 订单编号
}

步骤2:实现事务监听器(核心)

事务监听器是生产者端的核心,需实现 RocketMQLocalTransactionListener,包含「执行本地事务」和「回查事务状态」两个方法:

java 复制代码
import org.apache.rocketmq.spring.annotation.RocketMQTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionState;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Component;

/**
 * 订单事务消息监听器
 * @RocketMQTransactionListener:标记为事务监听器,绑定生产者组
 */
@Component
@RocketMQTransactionListener(txProducerGroup = "order-transaction-group")
public class OrderTransactionListener implements RocketMQLocalTransactionListener {

    /**
     * 第一步:执行本地事务(半消息发送成功后触发)
     * @param msg 半消息内容
     * @param arg 自定义参数(发送消息时传入)
     * @return 事务状态:COMMIT/ROLLBACK/UNKNOWN
     */
    @Override
    public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) {
        try {
            // 1. 解析消息(获取订单数据)
            OrderTransactionMessage orderMsg = (OrderTransactionMessage) msg.getPayload();
            System.out.println("【执行本地事务】开始处理订单:orderId=" + orderMsg.getOrderId());

            // 2. 执行本地事务核心逻辑(如订单入库、库存预扣减)
            // 真实场景:调用订单Service保存订单,调用库存Service预扣减库存
            boolean isSuccess = saveOrder(orderMsg); // 模拟订单入库

            // 3. 根据本地事务结果返回状态
            if (isSuccess) {
                System.out.println("【本地事务成功】订单入库完成,提交消息");
                return RocketMQLocalTransactionState.COMMIT; // 提交消息(Broker投递)
            } else {
                System.out.println("【本地事务失败】订单入库失败,回滚消息");
                return RocketMQLocalTransactionState.ROLLBACK; // 回滚消息(Broker删除)
            }
        } catch (Exception e) {
            System.out.println("【本地事务异常】未知状态,等待Broker回查:" + e.getMessage());
            // 异常时返回UNKNOWN,Broker会定时回查事务状态
            return RocketMQLocalTransactionState.UNKNOWN;
        }
    }

    /**
     * 第二步:事务回查(Broker对UNKNOWN状态的消息触发)
     * @param msg 半消息内容
     * @return 事务状态:COMMIT/ROLLBACK/UNKNOWN(建议仅返回前两者)
     */
    @Override
    public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
        // 1. 解析消息
        OrderTransactionMessage orderMsg = (OrderTransactionMessage) msg.getPayload();
        System.out.println("【事务回查】检查订单状态:orderId=" + orderMsg.getOrderId());

        // 2. 查数据库/缓存,确认本地事务最终状态
        // 真实场景:查询订单表,判断订单是否已入库
        boolean isOrderSuccess = checkOrderStatus(orderMsg.getOrderId());

        // 3. 返回最终状态(Broker根据此状态决定提交/回滚)
        if (isOrderSuccess) {
            System.out.println("【回查结果】订单已入库,提交消息");
            return RocketMQLocalTransactionState.COMMIT;
        } else {
            System.out.println("【回查结果】订单未入库,回滚消息");
            return RocketMQLocalTransactionState.ROLLBACK;
        }
    }

    // ------------------- 模拟业务方法 -------------------
    /**
     * 模拟订单入库
     */
    private boolean saveOrder(OrderTransactionMessage msg) {
        // 真实场景:执行insert into t_order ...
        // 模拟成功:return true;模拟失败:return false;模拟异常:throw new RuntimeException()
        return true;
    }

    /**
     * 模拟查询订单状态(回查时调用)
     */
    private boolean checkOrderStatus(Long orderId) {
        // 真实场景:select * from t_order where order_id = ?
        return true;
    }
}

步骤3:发送事务消息(生产者)

创建生产者 Service,调用 RocketMQTemplatesendMessageInTransaction 方法发送事务消息:

java 复制代码
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;

/**
 * 订单事务消息生产者
 */
@Service
public class OrderTransactionProducer {
    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    /**
     * 发送订单事务消息
     * @param orderMsg 订单消息
     */
    public void sendOrderTransactionMessage(OrderTransactionMessage orderMsg) {
        // 1. 构建消息(topic:order_topic,tag:transaction)
        org.springframework.messaging.Message<OrderTransactionMessage> message = MessageBuilder
                .withPayload(orderMsg)
                .build();

        // 2. 发送事务消息
        // 参数:topic:tag、消息体、自定义参数(传递给本地事务方法)
        rocketMQTemplate.sendMessageInTransaction(
                "order_topic:transaction",
                message,
                null // 自定义参数,可传递订单相关数据
        );
        System.out.println("【事务消息发送】半消息已发送:orderId=" + orderMsg.getOrderId());
    }
}

步骤4:消费事务消息(消费者)

消费者仅能收到「提交状态」的事务消息,实现普通消费逻辑即可:

java 复制代码
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;

/**
 * 订单事务消息消费者(仅消费提交的消息)
 */
@Component
@RocketMQMessageListener(
        topic = "order_topic",
        consumerGroup = "order-consumer-group",
        selectorExpression = "transaction" // 匹配tag
)
public class OrderTransactionConsumer implements RocketMQListener<OrderTransactionMessage> {

    @Override
    public void onMessage(OrderTransactionMessage msg) {
        // 消费逻辑:如扣减实际库存、发送短信通知、生成物流单等
        System.out.println("【消费事务消息】开始处理:orderId=" + msg.getOrderId());
        // 真实场景:stockService.deductStock(msg.getProductId(), msg.getNum());
        //          smsService.sendNotify(msg.getOrderNo());
        System.out.println("【消费完成】订单下游业务处理完毕:orderNo=" + msg.getOrderNo());
    }
}

步骤5:测试验证

创建测试控制器,模拟订单创建并发送事务消息:

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class OrderTestController {
    @Autowired
    private OrderTransactionProducer producer;

    /**
     * 测试发送事务消息
     * 访问:http://localhost:8080/createOrder/1001/1/2
     */
    @GetMapping("/createOrder/{orderId}/{productId}/{num}")
    public String createOrder(
            @PathVariable Long orderId,
            @PathVariable Long productId,
            @PathVariable Integer num) {
        // 构建订单消息
        OrderTransactionMessage msg = new OrderTransactionMessage();
        msg.setOrderId(orderId);
        msg.setProductId(productId);
        msg.setNum(num);
        msg.setOrderNo("ORDER_" + System.currentTimeMillis());

        // 发送事务消息
        producer.sendOrderTransactionMessage(msg);
        return "事务消息发送成功,订单号:" + msg.getOrderNo();
    }
}
测试结果(本地事务成功)

控制台输出:

复制代码
【事务消息发送】半消息已发送:orderId=1001
【执行本地事务】开始处理订单:orderId=1001
【本地事务成功】订单入库完成,提交消息
【消费事务消息】开始处理:orderId=1001
【消费完成】订单下游业务处理完毕:orderNo=ORDER_1710999999999
测试结果(本地事务异常)

修改 saveOrder 方法抛出异常,输出:

复制代码
【事务消息发送】半消息已发送:orderId=1002
【执行本地事务】开始处理订单:orderId=1002
【本地事务异常】未知状态,等待Broker回查:模拟异常
(Broker 定时回查)
【事务回查】检查订单状态:orderId=1002
【回查结果】订单未入库,回滚消息
(消费者无消费日志,消息被Broker删除)

三、RocketMQ 事务消息核心使用场景

场景1:电商下单(订单+库存+物流)

  • 痛点:订单入库、库存扣减、物流创建跨服务,需保证一致性;
  • 方案
    1. 生产者发送「订单创建」半消息;
    2. 本地事务执行「订单入库 + 库存预扣减」;
    3. 提交消息后,消费者执行「实际扣减库存 + 创建物流单」;
    4. 若本地事务失败,回滚消息,避免库存无效扣减。

场景2:支付回调(支付结果+订单状态更新)

  • 痛点:第三方支付回调异步通知,需保证「支付成功」与「订单改已支付」一致;
  • 方案
    1. 支付回调服务发送「支付结果」半消息;
    2. 本地事务更新订单状态为「已支付」;
    3. 提交消息后,消费者执行「发放优惠券 + 积分增加」;
    4. 若更新订单失败,回滚消息,支付平台可重试回调。

场景3:金融转账(转出+转入)

  • 痛点:A账户扣款、B账户加款需原子性,跨库/跨服务无法用本地事务;
  • 方案
    1. 转账服务发送「转账请求」半消息;
    2. 本地事务执行「A账户扣款」;
    3. 提交消息后,消费者执行「B账户加款」;
    4. 若A账户扣款失败,回滚消息;若扣款成功但B加款失败,通过消息重试保证最终一致性。

场景4:数据同步(业务库+ES/缓存)

  • 痛点:业务数据入库后,需同步到ES/Redis,避免「入库成功、同步失败」;
  • 方案
    1. 业务服务发送「数据变更」半消息;
    2. 本地事务执行「数据入库」;
    3. 提交消息后,消费者执行「ES/Redis同步」;
    4. 同步失败可通过消息重试机制补全。

场景5:跨系统通知(订单+短信/邮件)

  • 痛点:订单创建后需发送短信/邮件,避免「订单创建失败但短信已发送」;
  • 方案
    1. 订单服务发送「订单创建」半消息;
    2. 本地事务执行「订单入库」;
    3. 提交消息后,消费者执行「发送短信/邮件」;
    4. 订单入库失败则回滚消息,避免无效通知。

四、避坑要点(高频问题)

1. 事务监听器不生效

  • 原因:@RocketMQTransactionListenertxProducerGroup 与生产者组不一致;
  • 解决:保证监听器的 txProducerGroup = application.ymlproducer.group

2. 回查方法被频繁调用

  • 原因:本地事务返回 UNKNOWN 且回查方法多次返回 UNKNOWN
  • 解决:
    1. 回查方法必须返回 COMMIT/ROLLBACK,避免返回 UNKNOWN
    2. 优化回查逻辑,确保能快速获取事务最终状态;
    3. 调整 Broker 回查参数(transactionTimeout 超时时间、transactionCheckMax 最大回查次数)。

3. 消息重复消费

  • 原因:Broker 重试投递、消费者ACK失败;
  • 解决:
    1. 消费者实现幂等消费(如根据订单ID判断是否已处理);
    2. 消息携带唯一ID,消费前查缓存/数据库是否已处理。

4. 本地事务与消息提交不一致

  • 原因:本地事务提交成功,但提交消息时网络异常;
  • 解决:依赖 Broker 回查机制,回查方法确认本地事务成功后提交消息。

5. 事务消息性能问题

  • 原因:半消息存储、回查机制增加开销;
  • 优化:
    1. 批量发送事务消息(非高频场景);
    2. 缩短本地事务执行时间(避免长事务);
    3. 调整回查频率,避免高频回查。

五、核心总结

  1. 核心机制:RocketMQ 事务消息通过「半消息 + 本地事务 + 提交/回滚 + 回查」保证分布式事务最终一致性;
  2. 实现关键
    • 生产者:调用 sendMessageInTransaction 发送半消息;
    • 监听器:实现 executeLocalTransaction(执行本地事务)和 checkLocalTransaction(回查);
    • 消费者:仅消费提交状态的消息,实现幂等逻辑;
  3. 适用场景:跨服务/跨库的最终一致性场景(电商、支付、金融、数据同步);
  4. 避坑关键
    • 监听器与生产者组一致;
    • 回查方法返回明确状态;
    • 消费者实现幂等;
    • 避免本地事务执行时间过长。

RocketMQ 事务消息不保证「强一致性」,但能保证「最终一致性」,是分布式系统中解决跨服务事务的首选方案,相比 Seata 等框架,更轻量、无侵入,适合中小规模分布式场景。

相关推荐
乐观的Terry3 小时前
RocketMQ 使用指南
rocketmq
Nandeska7 小时前
1、RocketMQ核心概念详解
中间件·rocketmq
殷紫川10 小时前
RocketMQ 两大核心特性深度拆解:事务消息与延时消息,从原理到实战全打通
架构·rocketmq
程序员Terry1 天前
RocketMQ 使用指南
后端·rocketmq
耗子会飞1 天前
小白学习springboot项目如何连接RocketMQ
后端·rocketmq
IT界的老黄牛1 天前
RocketMQ 5.x 集群部署实战:3 台机器搞定 2 主 2 从,Docker Host 模式一把梭
docker·容器·rocketmq
乐观的Terry1 天前
Docker 部署 RocketMQ 5.1.0 踩坑实录:从超时到 Console 连不上的完整解决之路
docker·容器·rocketmq
不爱学英文的码字机器2 天前
Apache RocketMQ+cpolar 让消息服务全网可达
apache·rocketmq