生态整合与实战篇:Spring Boot 整合 RocketMQ 完全指南

系列第七阶段:生态整合与实战篇(一)

你好,又见面了。

从入门到源码,我们已经走过了 RocketMQ 学习的六个阶段。可以说,你对 RocketMQ 的"内功"已经相当扎实了。但从"懂原理"到"能落地",中间还差最后一步------把 RocketMQ 真正用到你的 Spring Boot 项目里去

今天这篇文章,就是帮你完成这最后一步的"临门一脚"。我们会从依赖选型开始,一步步搭建一个完整的 Spring Boot + RocketMQ 项目,涵盖生产者、消费者、事务消息、多环境配置等所有生产级要素。老规矩,配合代码和流程图,一步一图。

十七、Spring Boot 整合

rocketmq-spring-boot-starter 的版本选择与依赖引入

在开始写代码之前,我们面临第一个选择题:用哪个版本的 Starter?

这看似是个小问题,但在 Spring Boot 3.x 时代,版本选不对,项目可能连启动都起不来。

版本选型的核心原则

Spring Boot 版本 推荐 Starter 版本 说明
Spring Boot 2.x 2.2.3 社区验证最稳,生产案例最多
Spring Boot 3.x 2.2.3+ 2.2.3 已支持 Jakarta EE,兼容 Spring Boot 3
需要 RocketMQ 5.x 新特性 2.3.x 可用,但生产案例相对较少

⚠️ 避坑提示 :2.2.0 以下版本使用 javax.* 包,与 Spring Boot 3.x 的 jakarta.* 不兼容,直接报错。

Maven 依赖(以最稳定的 2.2.3 为例):

xml 复制代码
<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-spring-boot-starter</artifactId>
    <version>2.2.3</version>
</dependency>

这个 Starter 已经传递依赖了 rocketmq-client,所以你不需要再单独引入客户端依赖。但如果想精确控制客户端版本和服务端对齐,可以额外声明:

xml 复制代码
<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-client</artifactId>
    <version>5.1.0</version>  <!-- 与服务端版本保持一致 -->
</dependency>

生产者的配置与使用

基础配置application.yml):

yaml 复制代码
rocketmq:
  name-server: 127.0.0.1:9876  # NameServer 地址,多个用分号分隔
  producer:
    group: order-producer-group  # 生产者组名
    send-message-timeout: 3000   # 发送超时时间(毫秒)
    retry-times-when-send-failed: 2  # 同步发送失败重试次数
    retry-next-server: true      # 失败后是否换 Broker 重试
    compress-msg-body-over-how-much: 4096  # 超过多少字节压缩

生产级配置建议

  • NameServer 至少配置 2 个地址,避免单点故障
  • retry-next-server: true 开启后,发送失败会自动换 Broker 重试,提升可用性
  • 不要完全依赖自动重试解决所有问题,业务层必须有兜底方案

生产者代码

java 复制代码
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.apache.rocketmq.spring.support.RocketMQHeaders;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;

@Service
public class OrderProducer {

    private final RocketMQTemplate rocketMQTemplate;

    public OrderProducer(RocketMQTemplate rocketMQTemplate) {
        this.rocketMQTemplate = rocketMQTemplate;
    }

    /**
     * 同步发送消息(最常用)
     */
    public SendResult sendOrder(String orderId, String content) {
        // destination 格式:topic:tag
        String destination = "order-topic:order-create";
        
        Message<String> message = MessageBuilder
            .withPayload(content)
            .setHeader(RocketMQHeaders.KEYS, orderId)  // 设置业务 Key,用于查询和幂等
            .build();
        
        SendResult result = rocketMQTemplate.syncSend(destination, message);
        // 生产环境需要检查 result.getSendStatus()
        return result;
    }

    /**
     * 异步发送消息
     */
    public void sendOrderAsync(String orderId, String content) {
        String destination = "order-topic:order-create";
        Message<String> message = MessageBuilder
            .withPayload(content)
            .setHeader(RocketMQHeaders.KEYS, orderId)
            .build();
        
        rocketMQTemplate.asyncSend(destination, message, sendResult -> {
            // 回调处理
            if (sendResult.getSendStatus().name().equals("SEND_OK")) {
                System.out.println("异步发送成功:" + sendResult.getMsgId());
            }
        });
    }

    /**
     * 单向发送(不关心结果,最快)
     */
    public void sendOrderOneway(String orderId, String content) {
        String destination = "order-topic:order-create";
        Message<String> message = MessageBuilder
            .withPayload(content)
            .setHeader(RocketMQHeaders.KEYS, orderId)
            .build();
        
        rocketMQTemplate.sendOneWay(destination, message);
    }
}

KEY 的作用(非常重要):

设置 RocketMQHeaders.KEYS 有三个核心用途:

  1. 消息查询:在 Dashboard 中按业务 Key 快速定位消息
  2. 事务回查:事务消息回查时用于关联业务数据
  3. 幂等控制:消费者端用 Key 做去重判断

消费者的配置与使用

基础配置application.yml):

yaml 复制代码
rocketmq:
  name-server: 127.0.0.1:9876
  consumer:
    group: order-consumer-group  # 消费者组名
    consume-mode: CLUSTERING     # 消费模式:CLUSTERING(集群)或 BROADCASTING(广播)
    consume-thread-min: 5        # 最小消费线程数
    consume-thread-max: 20       # 最大消费线程数
    consume-message-batch-max-size: 1  # 批量消费最大条数
    pull-batch-size: 32          # 批量拉取最大条数

消费者代码 (使用 @RocketMQMessageListener 注解):

java 复制代码
import org.apache.rocketmq.spring.annotation.ConsumeMode;
import org.apache.rocketmq.spring.annotation.MessageModel;
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 = "order-create || order-pay",  // Tag 过滤,* 表示全部
    consumeMode = ConsumeMode.CONCURRENTLY,            // 并发消费
    messageModel = MessageModel.CLUSTERING,            // 集群模式
    maxReconsumeTimes = 16                             // 最大重试次数,-1 表示 16 次
)
public class OrderConsumer implements RocketMQListener<String> {

    @Override
    public void onMessage(String message) {
        // 1️⃣ 幂等校验(最重要!)
        // 2️⃣ 业务处理
        System.out.println("消费订单消息:" + message);
    }
}

生产铁律:一定要做幂等

幂等方式 适用场景
数据库唯一键 订单、账务等有明确业务 ID 的场景
Redis SETNX 高并发场景,快速去重
消息 KEY 通用方案,配合业务状态判断

事务消息的整合与实现

事务消息是 RocketMQ 最有价值、也最容易用错的功能。它的核心是保证"本地事务"和"消息发送"要么一起成功,要么一起失败。

事务消息的完整流程
sequenceDiagram participant App as 业务应用 participant P as Producer participant B as Broker participant DB as 本地数据库 App->>P: 1. 发送事务消息 P->>B: 2. 发送半消息(Half Message) B->>B: 3. 半消息持久化,暂不可消费 B-->>P: 4. 半消息发送成功 P->>App: 5. 回调执行本地事务 App->>DB: 6. 执行本地事务(如更新订单状态) alt 本地事务成功 DB-->>App: 7a. 事务提交成功 App-->>P: 8a. 返回 COMMIT P->>B: 9a. 提交事务 B->>B: 10a. 半消息→正式消息,可消费 else 本地事务失败 DB-->>App: 7b. 事务回滚 App-->>P: 8b. 返回 ROLLBACK P->>B: 9b. 回滚事务 B->>B: 10b. 删除半消息 else 事务状态未知(异常/超时) App-->>P: 8c. 返回 UNKNOWN Note over P,B: Broker 未收到最终确认,触发回查 loop 事务回查(默认每 60 秒) B->>P: 9c. 发起回查请求 P->>App: 10c. 检查本地事务状态 App->>DB: 11c. 查询业务数据 DB-->>App: 12c. 返回查询结果 App-->>P: 13c. 返回 COMMIT/ROLLBACK P->>B: 14c. 提交最终事务状态 end end

第一步:定义事务监听器

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.Service;

@Service
@RocketMQTransactionListener(txProducerGroup = "order-tx-producer-group")  // 必须与发送方组名一致
public class OrderTransactionListener implements RocketMQLocalTransactionListener {

    @Autowired
    private OrderService orderService;

    /**
     * 执行本地事务
     */
    @Override
    public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) {
        String orderId = (String) msg.getHeaders().get("orderId");
        
        try {
            // 执行本地业务:更新订单状态
            boolean success = orderService.updateOrderStatus(orderId, "PAID");
            
            // 根据执行结果返回 COMMIT 或 ROLLBACK
            return success ? RocketMQLocalTransactionState.COMMIT 
                           : RocketMQLocalTransactionState.ROLLBACK;
        } catch (Exception e) {
            // 返回 UNKNOWN,等待 Broker 回查
            return RocketMQLocalTransactionState.UNKNOWN;
        }
    }

    /**
     * 事务回查方法
     */
    @Override
    public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
        String orderId = (String) msg.getHeaders().get("orderId");
        
        // 查询本地事务状态
        String status = orderService.getOrderStatus(orderId);
        
        if ("PAID".equals(status)) {
            return RocketMQLocalTransactionState.COMMIT;
        } else if ("CANCELLED".equals(status)) {
            return RocketMQLocalTransactionState.ROLLBACK;
        }
        // 状态仍未知,继续等待下次回查
        return RocketMQLocalTransactionState.UNKNOWN;
    }
}

第二步:发送事务消息

java 复制代码
@Service
public class OrderTransactionProducer {

    private final RocketMQTemplate rocketMQTemplate;

    public OrderTransactionProducer(RocketMQTemplate rocketMQTemplate) {
        this.rocketMQTemplate = rocketMQTemplate;
    }

    public void createOrderWithTransaction(String orderId) {
        String destination = "order-tx-topic:order-create";
        
        Message<String> message = MessageBuilder
            .withPayload("订单创建:" + orderId)
            .setHeader("orderId", orderId)           // 传递给事务监听器
            .setHeader(RocketMQHeaders.KEYS, orderId)
            .build();
        
        // 发送事务消息
        rocketMQTemplate.sendMessageInTransaction(destination, message, null);
    }
}

消息监听器的多种用法

@RocketMQMessageListener 注解支持丰富的配置选项:

1. 按 Tag 过滤

java 复制代码
@RocketMQMessageListener(
    topic = "order-topic",
    consumerGroup = "order-consumer-group",
    selectorExpression = "order-create || order-pay"  // 只消费指定 Tag
)

2. 按 SQL92 表达式过滤

java 复制代码
@RocketMQMessageListener(
    topic = "order-topic",
    consumerGroup = "order-consumer-group",
    selectorType = SelectorType.SQL92,                // 使用 SQL92 过滤
    selectorExpression = "amount > 1000 AND region = 'SH'"  // SQL92 表达式
)

3. 顺序消费

java 复制代码
@RocketMQMessageListener(
    topic = "order-topic",
    consumerGroup = "order-consumer-group",
    consumeMode = ConsumeMode.ORDERLY                 // 顺序消费模式
)
public class OrderlyConsumer implements RocketMQListener<String> {
    @Override
    public void onMessage(String message) {
        // 同一个 Queue 的消息会按顺序被消费
    }
}

4. 广播消费

java 复制代码
@RocketMQMessageListener(
    topic = "order-topic",
    consumerGroup = "order-consumer-group",
    messageModel = MessageModel.BROADCASTING          // 广播模式
)
public class BroadcastConsumer implements RocketMQListener<String> {
    @Override
    public void onMessage(String message) {
        // 每个消费者实例都会收到这条消息
    }
}

5. 接收原始 MessageExt(获取更多元数据)

java 复制代码
import org.apache.rocketmq.common.message.MessageExt;

@RocketMQMessageListener(
    topic = "order-topic",
    consumerGroup = "order-consumer-group"
)
public class FullConsumer implements RocketMQListener<MessageExt> {
    @Override
    public void onMessage(MessageExt message) {
        String msgId = message.getMsgId();
        String body = new String(message.getBody());
        String tags = message.getTags();
        long bornTime = message.getBornTimestamp();
        // 可以获取更丰富的消息元数据
    }
}

消费者线程池配置

@RocketMQMessageListener 中的线程池配置:

参数 默认值 说明
consumeThreadNumber 20 消费线程数(2.2.3+ 新参数,推荐使用)
consumeThreadMax 64 已废弃,5.x 不再推荐使用

配置示例

java 复制代码
@RocketMQMessageListener(
    topic = "order-topic",
    consumerGroup = "order-consumer-group",
    consumeThreadNumber = 40  // 调大线程数提升并发消费能力
)

线程池调优建议

  • 消息处理逻辑轻量(如简单计算)→ 线程数可设大一些(如 40-60)
  • 消息处理逻辑重量(如调用第三方 API、复杂数据库操作)→ 线程数设小一些(如 10-20),避免资源争抢
  • 监控消费 TPS 和系统负载,动态调整

消息转换器的使用

RocketMQ Spring Boot Starter 默认使用 RocketMQMessageConverter 进行消息序列化和反序列化。

默认行为

  • 发送时:对象 → JSON 字符串
  • 接收时:JSON 字符串 → 目标类型

自定义消息转换器

java 复制代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.converter.MessageConverter;

@Configuration
public class RocketMQConfig {

    @Bean
    public MessageConverter rocketMQMessageConverter() {
        // 自定义转换逻辑
        return new CustomMessageConverter();
    }
}

常见场景

  • 使用 Protobuf 替代 JSON,提升序列化性能和减小消息体积
  • 使用 Kryo 等高性能序列化框架
  • 处理特殊的数据格式(如二进制数据)

多环境配置与管理

在微服务架构中,不同环境(开发、测试、生产)通常共用或使用不同的 RocketMQ 集群,需要做好环境隔离。

方案一:Spring Profile + 占位符

yaml 复制代码
# application-dev.yml
rocketmq:
  name-server: localhost:9876
  producer:
    group: order-producer-group-dev
  consumer:
    group: order-consumer-group-dev

# application-prod.yml
rocketmq:
  name-server: ns1.prod:9876;ns2.prod:9876
  producer:
    group: order-producer-group
  consumer:
    group: order-consumer-group

启动时指定 Profile:java -jar app.jar --spring.profiles.active=prod

方案二:SPEL 表达式动态拼接

yaml 复制代码
rocketmq:
  name-server: ${ROCKETMQ_NAMESERVER:localhost:9876}
  producer:
    group: order-producer-group-${spring.profiles.active}

方案三:Topic 命名规范隔离

复制代码
# 开发环境
order-topic_dev

# 测试环境
order-topic_test

# 生产环境
order-topic

⚠️ 注意 :RocketMQ Topic 命名规则有限制,建议使用下划线 _ 而非横杠 - 作为分隔符。

完整的项目示例

下面是一个完整的 Spring Boot 项目结构,涵盖了以上所有内容:

项目结构

复制代码
rocketmq-spring-boot-demo/
├── pom.xml
├── src/
│   └── main/
│       ├── java/
│       │   └── com/example/rocketmq/
│       │       ├── RocketMQApplication.java
│       │       ├── config/
│       │       │   └── RocketMQConfig.java
│       │       ├── producer/
│       │       │   ├── OrderProducer.java
│       │       │   └── OrderTransactionProducer.java
│       │       ├── consumer/
│       │       │   ├── OrderConsumer.java
│       │       │   └── OrderTransactionListener.java
│       │       └── service/
│       │           └── OrderService.java
│       └── resources/
│           ├── application.yml
│           ├── application-dev.yml
│           └── application-prod.yml

完整 pom.xml

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.0</version>
        <relativePath/>
    </parent>

    <groupId>com.example</groupId>
    <artifactId>rocketmq-spring-boot-demo</artifactId>
    <version>1.0.0</version>

    <properties>
        <java.version>17</java.version>
        <rocketmq-spring.version>2.2.3</rocketmq-spring.version>
    </properties>

    <dependencies>
        <!-- Spring Boot Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- RocketMQ Spring Boot Starter -->
        <dependency>
            <groupId>org.apache.rocketmq</groupId>
            <artifactId>rocketmq-spring-boot-starter</artifactId>
            <version>${rocketmq-spring.version}</version>
        </dependency>

        <!-- 可选:精确控制客户端版本 -->
        <dependency>
            <groupId>org.apache.rocketmq</groupId>
            <artifactId>rocketmq-client</artifactId>
            <version>5.1.0</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>
</project>

完整 application.yml

yaml 复制代码
spring:
  application:
    name: rocketmq-demo

rocketmq:
  name-server: 127.0.0.1:9876
  producer:
    group: order-producer-group
    send-message-timeout: 3000
    retry-times-when-send-failed: 2
    retry-next-server: true
  consumer:
    group: order-consumer-group
    consume-mode: CLUSTERING
    consume-thread-min: 5
    consume-thread-max: 20

# 事务消息的生产者组单独配置
tx:
  rocketmq:
    producer:
      group: order-tx-producer-group

启动类

java 复制代码
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class RocketMQApplication {
    public static void main(String[] args) {
        SpringApplication.run(RocketMQApplication.class, args);
    }
}

可扩展点总结

扩展点 实现方式 用途
自定义队列选择器 实现 MessageQueueSelector 控制消息发往哪个 Queue
自定义消息转换器 实现 MessageConverter 自定义序列化/反序列化
事务监听器 实现 RocketMQLocalTransactionListener 事务消息的本地事务和回查
消息过滤器 使用 SelectorType.SQL92 基于属性做复杂过滤
消费者线程池 配置 consumeThreadNumber 调整消费并发度
多环境配置 Spring Profile + 占位符 环境隔离

小结

这篇文章我们完整走通了 Spring Boot 整合 RocketMQ 的全流程,通过代码示例和流程图,搞清楚了:

  • 版本选择:Spring Boot 2.x 用 2.2.3,Spring Boot 3.x 也用 2.2.3+
  • 生产者配置与使用:同步/异步/单向三种发送方式,KEY 的三个核心用途
  • 消费者配置与使用@RocketMQMessageListener 注解的各种参数
  • 事务消息整合RocketMQLocalTransactionListener 的 executeLocalTransaction 和 checkLocalTransaction 两个方法
  • 监听器的多种用法:Tag 过滤、SQL92 过滤、顺序消费、广播消费、接收 MessageExt
  • 线程池配置consumeThreadNumber 替代已废弃的 consumeThreadMax
  • 消息转换器:自定义序列化/反序列化逻辑
  • 多环境配置:Profile + 占位符 + Topic 命名规范三种方案

现在,你已经可以在 Spring Boot 项目中真正用起来 RocketMQ 了。从依赖选型到生产级配置,从普通消息到事务消息,从单环境到多环境------你需要的所有能力,这篇文章都覆盖了。

下一篇,我们将进入 RocketMQ 与 Spring Cloud Stream 的整合,看看如何在微服务架构中更优雅地使用消息中间件。敬请期待!


系列文章:

  1. 入门认知篇 ✅
  2. 核心概念与架构篇 ✅
  3. 存储与原理篇(上)✅
  4. 存储与原理篇(中)✅
  5. 存储与原理篇(下)✅
  6. 事务消息 ✅
  7. 进阶应用篇 ✅
  8. 部署与运维篇 ✅
  9. 源码深入篇 ✅
  10. 生态整合与实战篇(一)✅(本文)
  11. 生态整合与实战篇(二)(待续...)