Spring Cloud Alibaba RocketMQ 实战:从底层原理到微服务落地全攻略

作为微服务架构中的核心组件,消息队列承担着解耦、削峰、异步通信等关键职责。而 RocketMQ 凭借其高吞吐、低延迟、高可靠的特性,成为微服务场景下的首选消息中间件之一。Spring Cloud Alibaba 作为主流的微服务生态,提供了对 RocketMQ 的无缝集成支持,让开发者能够快速实现消息驱动的微服务架构。

本文将从底层原理出发,结合实战案例,全面讲解 Spring Cloud Alibaba RocketMQ 的集成与应用。从环境搭建到核心功能实战,从微服务场景落地到性能优化,全程干货满满,所有示例均基于 JDK 17 编写,严格遵循《阿里巴巴 Java 开发手册(嵩山版)》,确保代码可直接运行。

一、核心认知:RocketMQ 与 Spring Cloud Alibaba 集成原理

在动手实战前,我们必须先搞懂底层逻辑:Spring Cloud Alibaba 是如何将 RocketMQ 融入微服务生态的?RocketMQ 的核心组件又扮演着什么角色?

1.1 RocketMQ 核心组件与架构

RocketMQ 采用分布式架构设计,核心组件包括:

  • NameServer:命名服务,负责路由管理(Broker 注册、Topic 路由信息维护),无状态设计,支持集群部署
  • Broker:消息存储与转发核心,负责接收生产者消息、存储消息、向消费者推送消息,支持主从架构
  • Producer:消息生产者,负责发送消息到 Broker
  • Consumer:消息消费者,负责从 Broker 订阅并消费消息
  • Topic:消息主题,用于消息分类,生产者向 Topic 发送消息,消费者从 Topic 订阅消息
  • Queue:Topic 的分区,用于并行处理消息,提高吞吐量

架构图如下:

1.2 Spring Cloud Alibaba RocketMQ 集成核心原理

Spring Cloud Alibaba 对 RocketMQ 的集成,核心是通过 spring-cloud-starter-stream-rocketmqrocketmq-spring-boot-starter 实现自动配置,底层逻辑如下:

  1. 自动配置类RocketMQAutoConfiguration 负责初始化核心组件(RocketMQTemplateDefaultMQProducerDefaultMQPushConsumer 等)
  2. 模板类封装RocketMQTemplate 封装了消息发送的所有操作(同步、异步、单向、事务等),简化 API 调用
  3. 注解驱动 :通过 @RocketMQMessageListener 注解声明消费者,自动注册到 RocketMQ 集群
  4. 配置绑定 :通过 application.yml 配置 RocketMQ 连接信息(NameServer 地址、生产者组、消费者组等),无需手动创建连接

集成流程图:

1.3 关键依赖说明

Spring Cloud Alibaba RocketMQ 集成的核心依赖分为两类:

  1. Spring Boot starters 依赖(推荐,自动配置更完善):

    <dependency> <groupId>org.apache.rocketmq</groupId> <artifactId>rocketmq-spring-boot-starter</artifactId> <version>2.2.3</version> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <version>2023.0.1.0</version> </dependency>
  2. 核心组件依赖(需手动配置,灵活度高):

    <dependency> <groupId>org.apache.rocketmq</groupId> <artifactId>rocketmq-client</artifactId> <version>5.2.0</version> </dependency> <dependency> <groupId>org.apache.rocketmq</groupId> <artifactId>rocketmq-common</artifactId> <version>5.2.0</version> </dependency>

二、环境搭建:从零到一部署 RocketMQ 与微服务项目

环境搭建是实战的基础,本节将分两步走:先部署 RocketMQ 服务端(单机 + 集群两种方案),再搭建 Spring Cloud Alibaba 微服务客户端项目。

2.1 RocketMQ 服务端部署(最新稳定版 5.2.0)

2.1.1 单机部署(开发 / 测试环境)

前提条件

  • JDK 17(必须,RocketMQ 5.x 最低要求 JDK 11,推荐 17)
  • 内存 ≥ 4GB(Broker 默认占用 2GB 堆内存)
  • 操作系统:Linux/macOS/Windows(推荐 Linux)

部署步骤

  1. 下载安装包

    官网下载(推荐)

    wget https://archive.apache.org/dist/rocketmq/5.2.0/rocketmq-all-5.2.0-bin-release.zip

    解压

    unzip rocketmq-all-5.2.0-bin-release.zip -d /usr/local/rocketmq
    cd /usr/local/rocketmq

  2. 修改配置文件(优化单机性能):

    修改NameServer配置(可选,默认端口9876)

    vi conf/namesrv.conf

    添加如下配置

    listenPort=9876
    storePathRootDir=/usr/local/rocketmq/data/namesrv

    修改Broker配置

    vi conf/broker.conf

    添加如下配置

    brokerClusterName=DefaultCluster
    brokerName=broker-a
    brokerId=0
    listenPort=10911
    namesrvAddr=127.0.0.1:9876
    storePathRootDir=/usr/local/rocketmq/data/broker
    storePathCommitLog=/usr/local/rocketmq/data/broker/commitlog
    autoCreateTopicEnable=true # 允许自动创建Topic(开发环境)

  3. 启动 NameServer

    后台启动,日志输出到namesrv.log

    nohup sh bin/mqnamesrv -c conf/namesrv.conf > logs/namesrv.log 2>&1 &

    验证启动成功(查看日志)

    tail -f logs/namesrv.log

    成功标识:The Name Server boot success

  4. 启动 Broker

    后台启动,日志输出到broker.log

    nohup sh bin/mqbroker -c conf/broker.conf > logs/broker.log 2>&1 &

    验证启动成功

    tail -f logs/broker.log

    成功标识:The broker[broker-a, 127.0.0.1:10911] boot success

  5. 关闭服务命令(后续备用):

    关闭Broker

    sh bin/mqshutdown broker

    关闭NameServer

    sh bin/mqshutdown namesrv

2.1.2 集群部署(生产环境)

生产环境推荐「2 主 2 从」架构,确保高可用,核心配置如下(以两台服务器为例):

服务器 IP 角色 端口配置 存储路径
192.168.1.100 NameServer+Broker 主 1 NameServer:9876, Broker:10911 /data/rocketmq/namesrv1, /data/rocketmq/broker1
192.168.1.101 NameServer+Broker 主 2 NameServer:9876, Broker:10911 /data/rocketmq/namesrv2, /data/rocketmq/broker2
192.168.1.102 Broker 从 1 Broker:10911 /data/rocketmq/broker1-slave
192.168.1.103 Broker 从 2 Broker:10911 /data/rocketmq/broker2-slave

核心配置文件(Broker 主 1)

复制代码
brokerClusterName=ProductionCluster
brokerName=broker-a
brokerId=0
listenPort=10911
namesrvAddr=192.168.1.100:9876;192.168.1.101:9876
storePathRootDir=/data/rocketmq/broker1
storePathCommitLog=/data/rocketmq/broker1/commitlog
brokerRole=SYNC_MASTER # 同步主节点(确保数据实时同步到从节点)
flushDiskType=ASYNC_FLUSH # 异步刷盘(平衡性能和可靠性)
autoCreateTopicEnable=false # 生产环境禁用自动创建Topic

Broker 从 1 配置

复制代码
brokerClusterName=ProductionCluster
brokerName=broker-a
brokerId=1
listenPort=10911
namesrvAddr=192.168.1.100:9876;192.168.1.101:9876
storePathRootDir=/data/rocketmq/broker1-slave
storePathCommitLog=/data/rocketmq/broker1-slave/commitlog
brokerRole=SLAVE # 从节点
flushDiskType=ASYNC_FLUSH

启动顺序:先启动所有 NameServer → 启动主 Broker → 启动从 Broker

2.2 微服务客户端项目搭建(Spring Cloud Alibaba)

2.2.1 项目结构设计

采用「父工程 + 子模块」架构,分为 3 个模块:

  • rocketmq-microservice-parent:父工程(统一管理依赖版本)
  • rocketmq-producer-service:消息生产者(订单服务)
  • rocketmq-consumer-service:消息消费者(库存服务、支付服务)
2.2.2 父工程 POM 配置
复制代码
<?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.5</version> <!-- 最新稳定版 -->
        <relativePath/>
    </parent>

    <groupId>com.ken.rocketmq</groupId>
    <artifactId>rocketmq-microservice-parent</artifactId>
    <version>1.0.0</version>
    <packaging>pom</packaging>
    <name>RocketMQ 微服务集成父工程</name>

    <!-- 子模块 -->
    <modules>
        <module>rocketmq-producer-service</module>
        <module>rocketmq-consumer-service</module>
    </modules>

    <!-- 统一依赖版本管理 -->
    <properties>
        <java.version>17</java.version>
        <spring-cloud-alibaba.version>2023.0.1.0</spring-cloud-alibaba.version>
        <rocketmq-spring-boot.version>2.2.3</rocketmq-spring-boot.version>
        <mybatis-plus.version>3.5.5</mybatis-plus.version>
        <fastjson2.version>2.0.48</fastjson2.version>
        <lombok.version>1.18.30</lombok.version>
        <springdoc-openapi.version>2.2.0</springdoc-openapi.version>
        <mysql-connector.version>8.0.36</mysql-connector.version>
    </properties>

    <!-- 依赖管理 -->
    <dependencyManagement>
        <dependencies>
            <!-- Spring Cloud Alibaba 生态 -->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring-cloud-alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

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

            <!-- MyBatis-Plus 依赖 -->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>${mybatis-plus.version}</version>
            </dependency>

            <!-- Fastjson2 依赖 -->
            <dependency>
                <groupId>com.alibaba.fastjson2</groupId>
                <artifactId>fastjson2</artifactId>
                <version>${fastjson2.version}</version>
            </dependency>

            <!-- Lombok 依赖 -->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>${lombok.version}</version>
                <scope>provided</scope>
            </dependency>

            <!-- Swagger3 (SpringDoc OpenAPI) -->
            <dependency>
                <groupId>org.springdoc</groupId>
                <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
                <version>${springdoc-openapi.version}</version>
            </dependency>

            <!-- MySQL 驱动 -->
            <dependency>
                <groupId>com.mysql</groupId>
                <artifactId>mysql-connector-j</artifactId>
                <version>${mysql-connector.version}</version>
                <scope>runtime</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <version>${spring-boot.version}</version>
                    <configuration>
                        <excludes>
                            <exclude>
                                <groupId>org.projectlombok</groupId>
                                <artifactId>lombok</artifactId>
                            </exclude>
                        </excludes>
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>
2.2.3 生产者模块(rocketmq-producer-service)

1. POM 依赖

复制代码
<?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">
    <parent>
        <groupId>com.ken.rocketmq</groupId>
        <artifactId>rocketmq-microservice-parent</artifactId>
        <version>1.0.0</version>
    </parent>

    <modelVersion>4.0.0</modelVersion>
    <artifactId>rocketmq-producer-service</artifactId>
    <name>消息生产者服务(订单服务)</name>

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

        <!-- Spring Cloud Alibaba Nacos 服务发现 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

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

        <!-- MyBatis-Plus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
        </dependency>

        <!-- MySQL 驱动 -->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
        </dependency>

        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>

        <!-- Fastjson2 -->
        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
        </dependency>

        <!-- Swagger3 -->
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
        </dependency>

        <!-- Google Collections -->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>33.1.0-jre</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

2. 配置文件(application.yml)

复制代码
server:
  port: 8081 # 生产者服务端口

spring:
  application:
    name: rocketmq-producer-service # 服务名称
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848 # Nacos服务地址(本地部署)
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/rocketmq_order?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
    username: root
    password: root123456
    driver-class-name: com.mysql.cj.jdbc.Driver

# RocketMQ 配置
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-times-when-send-async-failed: 2 # 异步发送失败重试次数
    max-message-size: 4194304 # 最大消息大小(4MB)
    compress-message-body-threshold: 4096 # 消息压缩阈值(4KB)

# MyBatis-Plus 配置
mybatis-plus:
  mapper-locations: classpath:mapper/*.xml # Mapper映射文件路径
  type-aliases-package: com.ken.rocketmq.producer.entity # 实体类别名包
  configuration:
    map-underscore-to-camel-case: true # 下划线转驼峰
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 打印SQL日志
  global-config:
    db-config:
      id-type: AUTO # 主键自增

# Swagger3 配置
springdoc:
  api-docs:
    path: /v3/api-docs
  swagger-ui:
    path: /swagger-ui.html
    operationsSorter: method
  packages-to-scan: com.ken.rocketmq.producer.controller # 扫描Controller包

# 日志配置
logging:
  level:
    root: INFO
    com.ken.rocketmq.producer: DEBUG
    org.apache.rocketmq: WARN

3. 启动类

复制代码
package com.ken.rocketmq.producer;

import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.info.Info;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

/**
 * 消息生产者启动类(订单服务)
 * @author ken
 */
@SpringBootApplication
@EnableDiscoveryClient // 启用服务发现(Nacos)
@MapperScan("com.ken.rocketmq.producer.mapper") // MyBatis-Plus Mapper扫描
@OpenAPIDefinition(
        info = @Info(
                title = "RocketMQ 生产者API文档",
                version = "1.0.0",
                description = "基于Spring Cloud Alibaba RocketMQ的消息生产者服务(订单服务)"
        )
)
public class RocketMQProducerApplication {
    public static void main(String[] args) {
        SpringApplication.run(RocketMQProducerApplication.class, args);
    }
}
2.2.4 消费者模块(rocketmq-consumer-service)

1. POM 依赖 :与生产者模块一致,仅需修改 artifactIdname

复制代码
<artifactId>rocketmq-consumer-service</artifactId>
<name>消息消费者服务(库存/支付服务)</name>

2. 配置文件(application.yml)

复制代码
server:
  port: 8082 # 消费者服务端口

spring:
  application:
    name: rocketmq-consumer-service # 服务名称
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848 # Nacos服务地址
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/rocketmq_inventory?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
    username: root
    password: root123456
    driver-class-name: com.mysql.cj.jdbc.Driver

# RocketMQ 配置
rocketmq:
  name-server: 127.0.0.1:9876 # NameServer地址
  consumer:
    group: inventory-consumer-group # 消费者组名称(库存服务)
    consume-thread-min: 20 # 最小消费线程数
    consume-thread-max: 50 # 最大消费线程数
    consume-message-batch-max-size: 10 # 批量消费最大消息数
    pull-batch-size: 32 # 拉取消息批量大小
    consume-timeout: 15 # 消费超时时间(分钟)

# MyBatis-Plus 配置(与生产者一致)
mybatis-plus:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.ken.rocketmq.consumer.entity
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      id-type: AUTO

# Swagger3 配置
springdoc:
  api-docs:
    path: /v3/api-docs
  swagger-ui:
    path: /swagger-ui.html
    operationsSorter: method
  packages-to-scan: com.ken.rocketmq.consumer.controller

# 日志配置
logging:
  level:
    root: INFO
    com.ken.rocketmq.consumer: DEBUG
    org.apache.rocketmq: WARN

3. 启动类

复制代码
package com.ken.rocketmq.consumer;

import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.info.Info;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

/**
 * 消息消费者启动类(库存/支付服务)
 * @author ken
 */
@SpringBootApplication
@EnableDiscoveryClient
@MapperScan("com.ken.rocketmq.consumer.mapper")
@OpenAPIDefinition(
        info = @Info(
                title = "RocketMQ 消费者API文档",
                version = "1.0.0",
                description = "基于Spring Cloud Alibaba RocketMQ的消息消费者服务(库存/支付服务)"
        )
)
public class RocketMQConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(RocketMQConsumerApplication.class, args);
    }
}

2.3 数据库表设计(MySQL 8.0)

2.3.1 订单库(rocketmq_order)
复制代码
CREATE DATABASE IF NOT EXISTS rocketmq_order DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE rocketmq_order;

-- 订单表
CREATE TABLE IF NOT EXISTS `t_order` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '订单ID',
  `order_no` varchar(64) NOT NULL COMMENT '订单编号',
  `user_id` bigint NOT NULL COMMENT '用户ID',
  `product_id` bigint NOT NULL COMMENT '商品ID',
  `quantity` int NOT NULL COMMENT '购买数量',
  `total_amount` decimal(10,2) NOT NULL COMMENT '订单总金额',
  `order_status` tinyint NOT NULL COMMENT '订单状态:0-待支付,1-已支付,2-已取消,3-已完成',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_order_no` (`order_no`),
  KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表';

-- 订单消息记录表(用于事务消息回查)
CREATE TABLE IF NOT EXISTS `t_order_message` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `order_no` varchar(64) NOT NULL COMMENT '订单编号',
  `message_id` varchar(64) DEFAULT NULL COMMENT 'RocketMQ消息ID',
  `message_status` tinyint NOT NULL COMMENT '消息状态:0-待发送,1-已发送,2-已确认,3-发送失败',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_order_no` (`order_no`),
  KEY `idx_message_id` (`message_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单消息记录表';
2.3.2 库存库(rocketmq_inventory)
复制代码
CREATE DATABASE IF NOT EXISTS rocketmq_inventory DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE rocketmq_inventory;

-- 商品库存表
CREATE TABLE IF NOT EXISTS `t_inventory` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `product_id` bigint NOT NULL COMMENT '商品ID',
  `stock_quantity` int NOT NULL COMMENT '库存数量',
  `lock_quantity` int NOT NULL DEFAULT '0' COMMENT '锁定库存数量',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_product_id` (`product_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品库存表';

-- 库存操作日志表
CREATE TABLE IF NOT EXISTS `t_inventory_log` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `product_id` bigint NOT NULL COMMENT '商品ID',
  `order_no` varchar(64) NOT NULL COMMENT '订单编号',
  `operate_type` tinyint NOT NULL COMMENT '操作类型:0-锁定库存,1-解锁库存,2-扣减库存',
  `operate_quantity` int NOT NULL COMMENT '操作数量',
  `operate_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '操作时间',
  PRIMARY KEY (`id`),
  KEY `idx_product_id` (`product_id`),
  KEY `idx_order_no` (`order_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='库存操作日志表';

三、核心功能实战:Spring Cloud Alibaba RocketMQ 关键特性

本节将通过实战案例,讲解 RocketMQ 的核心功能:简单消息、同步 / 异步 / 单向消息、顺序消息、事务消息、延迟消息、批量消息、过滤消息、死信队列,每个案例都提供完整可运行的代码。

3.1 基础:简单消息发送与消费

3.1.1 消息实体类
复制代码
package com.ken.rocketmq.common.entity;

import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;

/**
 * 订单消息实体
 * @author ken
 */
@Data
public class OrderMessage implements Serializable {
    private static final long serialVersionUID = 1L;

    /**
     * 订单编号
     */
    private String orderNo;

    /**
     * 用户ID
     */
    private Long userId;

    /**
     * 商品ID
     */
    private Long productId;

    /**
     * 购买数量
     */
    private Integer quantity;

    /**
     * 订单总金额
     */
    private BigDecimal totalAmount;

    /**
     * 消息发送时间
     */
    private LocalDateTime sendTime;
}
3.1.2 生产者:发送简单消息

1. Service 层

复制代码
package com.ken.rocketmq.producer.service;

import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ken.rocketmq.common.entity.OrderMessage;
import com.ken.rocketmq.producer.entity.TOrder;
import com.ken.rocketmq.producer.entity.TOrderMessage;
import com.ken.rocketmq.producer.mapper.TOrderMapper;
import com.ken.rocketmq.producer.mapper.TOrderMessageMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.UUID;

/**
 * 订单服务实现类
 * @author ken
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService {

    private final TOrderMapper orderMapper;
    private final TOrderMessageMapper orderMessageMapper;
    private final RocketMQTemplate rocketMQTemplate;

    /**
     * 订单Topic(在配置文件中定义,便于维护)
     */
    @Value("${rocketmq.topic.order-topic:order_topic}")
    private String orderTopic;

    /**
     * 创建订单并发送消息
     * @param userId 用户ID
     * @param productId 商品ID
     * @param quantity 购买数量
     * @param unitPrice 商品单价
     * @return 订单编号
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public String createOrder(Long userId, Long productId, Integer quantity, BigDecimal unitPrice) {
        // 1. 参数校验
        if (ObjectUtils.isEmpty(userId)) {
            throw new IllegalArgumentException("用户ID不能为空");
        }
        if (ObjectUtils.isEmpty(productId)) {
            throw new IllegalArgumentException("商品ID不能为空");
        }
        if (ObjectUtils.isEmpty(quantity) || quantity <= 0) {
            throw new IllegalArgumentException("购买数量必须大于0");
        }
        if (ObjectUtils.isEmpty(unitPrice) || unitPrice.compareTo(BigDecimal.ZERO) <= 0) {
            throw new IllegalArgumentException("商品单价必须大于0");
        }

        // 2. 生成订单编号
        String orderNo = UUID.randomUUID().toString().replace("-", "").substring(0, 20);

        // 3. 计算订单总金额
        BigDecimal totalAmount = unitPrice.multiply(new BigDecimal(quantity));

        // 4. 保存订单信息
        TOrder order = new TOrder();
        order.setOrderNo(orderNo);
        order.setUserId(userId);
        order.setProductId(productId);
        order.setQuantity(quantity);
        order.setTotalAmount(totalAmount);
        order.setOrderStatus((byte) 0); // 0-待支付
        order.setCreateTime(LocalDateTime.now());
        order.setUpdateTime(LocalDateTime.now());
        int insertCount = orderMapper.insert(order);
        log.info("创建订单成功,订单编号:{},插入行数:{}", orderNo, insertCount);

        // 5. 构建订单消息
        OrderMessage orderMessage = new OrderMessage();
        orderMessage.setOrderNo(orderNo);
        orderMessage.setUserId(userId);
        orderMessage.setProductId(productId);
        orderMessage.setQuantity(quantity);
        orderMessage.setTotalAmount(totalAmount);
        orderMessage.setSendTime(LocalDateTime.now());

        // 6. 发送消息(同步发送)
        Message<OrderMessage> message = MessageBuilder.withPayload(orderMessage).build();
        try {
            // 发送消息到Topic,无Tag
            rocketMQTemplate.send(orderTopic, message);
            log.info("订单消息发送成功,订单编号:{},消息内容:{}", orderNo, JSON.toJSONString(orderMessage));

            // 7. 更新消息状态为已发送
            TOrderMessage orderMessageRecord = new TOrderMessage();
            orderMessageRecord.setOrderNo(orderNo);
            orderMessageRecord.setMessageStatus((byte) 1); // 1-已发送
            orderMessageRecord.setCreateTime(LocalDateTime.now());
            orderMessageRecord.setUpdateTime(LocalDateTime.now());
            orderMessageMapper.insert(orderMessageRecord);
        } catch (Exception e) {
            log.error("订单消息发送失败,订单编号:{},异常信息:{}", orderNo, e.getMessage(), e);
            // 消息发送失败,抛出异常触发事务回滚
            throw new RuntimeException("订单创建失败:消息发送异常");
        }

        return orderNo;
    }
}

2. Controller 层

复制代码
package com.ken.rocketmq.producer.controller;

import com.ken.rocketmq.producer.service.OrderService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.math.BigDecimal;

/**
 * 订单Controller
 * @author ken
 */
@RestController
@RequestMapping("/api/order")
@Slf4j
@RequiredArgsConstructor
@Tag(name = "订单管理", description = "订单创建、查询等接口")
public class OrderController {

    private final OrderService orderService;

    /**
     * 创建订单
     * @param userId 用户ID
     * @param productId 商品ID
     * @param quantity 购买数量
     * @param unitPrice 商品单价
     * @return 订单编号
     */
    @PostMapping("/create")
    @Operation(summary = "创建订单", description = "创建订单并发送消息到RocketMQ")
    public ResponseEntity<String> createOrder(
            @Parameter(description = "用户ID", required = true) @RequestParam Long userId,
            @Parameter(description = "商品ID", required = true) @RequestParam Long productId,
            @Parameter(description = "购买数量", required = true) @RequestParam Integer quantity,
            @Parameter(description = "商品单价", required = true) @RequestParam BigDecimal unitPrice) {
        String orderNo = orderService.createOrder(userId, productId, quantity, unitPrice);
        return ResponseEntity.ok("订单创建成功,订单编号:" + orderNo);
    }
}
3.1.3 消费者:消费简单消息

1. Service 层(库存扣减)

复制代码
package com.ken.rocketmq.consumer.service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.ken.rocketmq.common.entity.OrderMessage;
import com.ken.rocketmq.consumer.entity.TInventory;
import com.ken.rocketmq.consumer.entity.TInventoryLog;
import com.ken.rocketmq.consumer.mapper.TInventoryMapper;
import com.ken.rocketmq.consumer.mapper.TInventoryLogMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;

import java.time.LocalDateTime;

/**
 * 库存服务实现类
 * @author ken
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class InventoryServiceImpl implements InventoryService {

    private final TInventoryMapper inventoryMapper;
    private final TInventoryLogMapper inventoryLogMapper;

    /**
     * 扣减库存
     * @param orderMessage 订单消息
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deductInventory(OrderMessage orderMessage) {
        // 1. 参数校验
        if (ObjectUtils.isEmpty(orderMessage)) {
            log.error("扣减库存失败:订单消息为空");
            throw new IllegalArgumentException("订单消息为空");
        }
        String orderNo = orderMessage.getOrderNo();
        Long productId = orderMessage.getProductId();
        Integer quantity = orderMessage.getQuantity();
        if (StringUtils.isEmpty(orderNo)) {
            log.error("扣减库存失败:订单编号为空");
            throw new IllegalArgumentException("订单编号为空");
        }
        if (ObjectUtils.isEmpty(productId)) {
            log.error("扣减库存失败:商品ID为空");
            throw new IllegalArgumentException("商品ID为空");
        }
        if (ObjectUtils.isEmpty(quantity) || quantity <= 0) {
            log.error("扣减库存失败:购买数量无效,订单编号:{}", orderNo);
            throw new IllegalArgumentException("购买数量无效");
        }

        // 2. 查询商品库存
        TInventory inventory = inventoryMapper.selectOne(new LambdaQueryWrapper<TInventory>()
                .eq(TInventory::getProductId, productId));
        if (ObjectUtils.isEmpty(inventory)) {
            log.error("扣减库存失败:商品不存在,商品ID:{},订单编号:{}", productId, orderNo);
            throw new RuntimeException("商品不存在");
        }

        // 3. 校验库存是否充足
        int currentStock = inventory.getStockQuantity();
        if (currentStock < quantity) {
            log.error("扣减库存失败:库存不足,商品ID:{},当前库存:{},需扣减:{},订单编号:{}",
                    productId, currentStock, quantity, orderNo);
            throw new RuntimeException("库存不足");
        }

        // 4. 扣减库存
        LambdaUpdateWrapper<TInventory> updateWrapper = new LambdaUpdateWrapper<TInventory>()
                .eq(TInventory::getProductId, productId)
                .ge(TInventory::getStockQuantity, quantity) // 乐观锁:确保库存充足
                .set(TInventory::getStockQuantity, currentStock - quantity)
                .set(TInventory::getUpdateTime, LocalDateTime.now());
        int updateCount = inventoryMapper.update(null, updateWrapper);
        if (updateCount <= 0) {
            log.error("扣减库存失败:更新库存行数为0,可能库存已被其他线程扣减,商品ID:{},订单编号:{}",
                    productId, orderNo);
            throw new RuntimeException("库存扣减失败,请重试");
        }
        log.info("扣减库存成功,商品ID:{},订单编号:{},扣减数量:{},剩余库存:{}",
                productId, orderNo, quantity, currentStock - quantity);

        // 5. 记录库存操作日志
        TInventoryLog inventoryLog = new TInventoryLog();
        inventoryLog.setProductId(productId);
        inventoryLog.setOrderNo(orderNo);
        inventoryLog.setOperateType((byte) 2); // 2-扣减库存
        inventoryLog.setOperateQuantity(quantity);
        inventoryLog.setOperateTime(LocalDateTime.now());
        inventoryLogMapper.insert(inventoryLog);
        log.info("库存操作日志记录成功,订单编号:{},日志内容:{}", orderNo, inventoryLog);
    }
}

2. 消息消费者监听类

复制代码
package com.ken.rocketmq.consumer.listener;

import com.alibaba.fastjson2.JSON;
import com.ken.rocketmq.common.entity.OrderMessage;
import com.ken.rocketmq.consumer.service.InventoryService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;

/**
 * 订单消息消费者(库存扣减)
 * @author ken
 */
@Component
@Slf4j
@RequiredArgsConstructor
@RocketMQMessageListener(
        topic = "${rocketmq.topic.order-topic:order_topic}", // 监听的Topic
        consumerGroup = "${rocketmq.consumer.group:inventory-consumer-group}", // 消费者组
        consumeMode = ConsumeMode.CONCURRENTLY, // 并发消费(默认)
        messageModel = MessageModel.CLUSTERING // 集群消费模式(默认)
)
public class OrderMessageConsumer implements RocketMQListener<OrderMessage> {

    private final InventoryService inventoryService;

    /**
     * 消费消息
     * @param message 订单消息
     */
    @Override
    public void onMessage(OrderMessage message) {
        log.info("收到订单消息,开始处理库存扣减,消息内容:{}", JSON.toJSONString(message));
        try {
            // 处理库存扣减
            inventoryService.deductInventory(message);
            log.info("订单消息处理成功,订单编号:{}", message.getOrderNo());
        } catch (Exception e) {
            log.error("订单消息处理失败,订单编号:{},异常信息:{}", message.getOrderNo(), e.getMessage(), e);
            // 抛出异常,触发消息重试(默认重试16次)
            throw new RuntimeException("库存扣减失败,触发消息重试", e);
        }
    }
}
3.1.4 测试验证
  1. 启动 Nacos、RocketMQ 服务端
  2. 启动生产者、消费者服务
  3. 访问 Swagger 地址:http://localhost:8081/swagger-ui.html
  4. 调用 /api/order/create 接口,传入参数(如 userId=1001,productId=2001,quantity=2,unitPrice=99.9)
  5. 查看生产者日志:确认订单创建成功,消息发送成功
  6. 查看消费者日志:确认消息接收成功,库存扣减成功
  7. 查看数据库:t_order 表新增订单记录,t_inventory 表库存减少,t_inventory_log 表新增日志

3.2 进阶:同步 / 异步 / 单向消息

RocketMQ 提供三种消息发送方式,适用于不同场景:

发送方式 特点 适用场景
同步发送 发送方阻塞,等待 Broker 响应,可靠性最高 重要消息(订单创建、支付通知)
异步发送 发送方非阻塞,通过回调函数处理响应,吞吐量高 非核心消息(日志、通知)
单向发送 发送方不等待响应,仅负责发送,性能最高 日志收集、监控数据上报
3.2.1 同步消息(已在 3.1 中实现)

核心 API:rocketMQTemplate.send(topic, message),返回 SendResult 对象,包含消息发送状态、消息 ID 等信息。

3.2.2 异步消息发送

1. Service 层新增方法

复制代码
/**
 * 创建订单并异步发送消息
 * @param userId 用户ID
 * @param productId 商品ID
 * @param quantity 购买数量
 * @param unitPrice 商品单价
 * @return 订单编号
 */
@Override
@Transactional(rollbackFor = Exception.class)
public String createOrderAsync(Long userId, Long productId, Integer quantity, BigDecimal unitPrice) {
    // 1. 参数校验(与同步发送一致)
    if (ObjectUtils.isEmpty(userId)) {
        throw new IllegalArgumentException("用户ID不能为空");
    }
    if (ObjectUtils.isEmpty(productId)) {
        throw new IllegalArgumentException("商品ID不能为空");
    }
    if (ObjectUtils.isEmpty(quantity) || quantity <= 0) {
        throw new IllegalArgumentException("购买数量必须大于0");
    }
    if (ObjectUtils.isEmpty(unitPrice) || unitPrice.compareTo(BigDecimal.ZERO) <= 0) {
        throw new IllegalArgumentException("商品单价必须大于0");
    }

    // 2. 生成订单编号
    String orderNo = UUID.randomUUID().toString().replace("-", "").substring(0, 20);

    // 3. 计算订单总金额并保存订单(与同步发送一致)
    BigDecimal totalAmount = unitPrice.multiply(new BigDecimal(quantity));
    TOrder order = new TOrder();
    order.setOrderNo(orderNo);
    order.setUserId(userId);
    order.setProductId(productId);
    order.setQuantity(quantity);
    order.setTotalAmount(totalAmount);
    order.setOrderStatus((byte) 0);
    order.setCreateTime(LocalDateTime.now());
    order.setUpdateTime(LocalDateTime.now());
    orderMapper.insert(order);
    log.info("创建订单成功(异步消息),订单编号:{}", orderNo);

    // 4. 构建订单消息
    OrderMessage orderMessage = new OrderMessage();
    orderMessage.setOrderNo(orderNo);
    orderMessage.setUserId(userId);
    orderMessage.setProductId(productId);
    orderMessage.setQuantity(quantity);
    orderMessage.setTotalAmount(totalAmount);
    orderMessage.setSendTime(LocalDateTime.now());

    // 5. 异步发送消息
    Message<OrderMessage> message = MessageBuilder.withPayload(orderMessage).build();
    rocketMQTemplate.asyncSend(orderTopic, message, new SendCallback() {
        @Override
        public void onSuccess(SendResult sendResult) {
            // 消息发送成功回调
            log.info("异步订单消息发送成功,订单编号:{},消息ID:{},发送结果:{}",
                    orderNo, sendResult.getMsgId(), JSON.toJSONString(sendResult));

            // 更新消息记录
            TOrderMessage orderMessageRecord = new TOrderMessage();
            orderMessageRecord.setOrderNo(orderNo);
            orderMessageRecord.setMessageId(sendResult.getMsgId());
            orderMessageRecord.setMessageStatus((byte) 1);
            orderMessageRecord.setCreateTime(LocalDateTime.now());
            orderMessageRecord.setUpdateTime(LocalDateTime.now());
            orderMessageMapper.insert(orderMessageRecord);
        }

        @Override
        public void onException(Throwable e) {
            // 消息发送失败回调
            log.error("异步订单消息发送失败,订单编号:{},异常信息:{}", orderNo, e.getMessage(), e);

            // 此处可进行失败重试或告警处理
            // 注意:异步发送失败不会触发事务回滚,需手动处理订单状态
            try {
                // 更新订单状态为创建失败
                LambdaUpdateWrapper<TOrder> updateWrapper = new LambdaUpdateWrapper<TOrder>()
                        .eq(TOrder::getOrderNo, orderNo)
                        .set(TOrder::getOrderStatus, (byte) 99) // 99-创建失败
                        .set(TOrder::getUpdateTime, LocalDateTime.now());
                orderMapper.update(null, updateWrapper);

                // 记录失败消息
                TOrderMessage orderMessageRecord = new TOrderMessage();
                orderMessageRecord.setOrderNo(orderNo);
                orderMessageRecord.setMessageStatus((byte) 3); // 3-发送失败
                orderMessageRecord.setCreateTime(LocalDateTime.now());
                orderMessageRecord.setUpdateTime(LocalDateTime.now());
                orderMessageMapper.insert(orderMessageRecord);
            } catch (Exception ex) {
                log.error("处理异步消息发送失败异常时出错,订单编号:{}", orderNo, ex);
            }
        }
    });

    return orderNo;
}

2. Controller 新增接口

复制代码
@PostMapping("/create-async")
@Operation(summary = "创建订单(异步消息)", description = "创建订单并异步发送消息到RocketMQ")
public ResponseEntity<String> createOrderAsync(
        @Parameter(description = "用户ID", required = true) @RequestParam Long userId,
        @Parameter(description = "商品ID", required = true) @RequestParam Long productId,
        @Parameter(description = "购买数量", required = true) @RequestParam Integer quantity,
        @Parameter(description = "商品单价", required = true) @RequestParam BigDecimal unitPrice) {
    String orderNo = orderService.createOrderAsync(userId, productId, quantity, unitPrice);
    return ResponseEntity.ok("异步订单创建成功,订单编号:" + orderNo);
}
3.2.3 单向消息发送

1. Service 层新增方法

复制代码
/**
 * 创建订单并单向发送消息(适用于非核心消息)
 * @param userId 用户ID
 * @param productId 商品ID
 * @param quantity 购买数量
 * @param unitPrice 商品单价
 * @return 订单编号
 */
@Override
@Transactional(rollbackFor = Exception.class)
public String createOrderOneWay(Long userId, Long productId, Integer quantity, BigDecimal unitPrice) {
    // 参数校验、订单创建逻辑与同步发送一致
    if (ObjectUtils.isEmpty(userId)) {
        throw new IllegalArgumentException("用户ID不能为空");
    }
    String orderNo = UUID.randomUUID().toString().replace("-", "").substring(0, 20);
    BigDecimal totalAmount = unitPrice.multiply(new BigDecimal(quantity));
    TOrder order = new TOrder();
    order.setOrderNo(orderNo);
    order.setUserId(userId);
    order.setProductId(productId);
    order.setQuantity(quantity);
    order.setTotalAmount(totalAmount);
    order.setOrderStatus((byte) 0);
    order.setCreateTime(LocalDateTime.now());
    order.setUpdateTime(LocalDateTime.now());
    orderMapper.insert(order);
    log.info("创建订单成功(单向消息),订单编号:{}", orderNo);

    // 构建消息并单向发送
    OrderMessage orderMessage = new OrderMessage();
    orderMessage.setOrderNo(orderNo);
    orderMessage.setUserId(userId);
    orderMessage.setProductId(productId);
    orderMessage.setQuantity(quantity);
    orderMessage.setTotalAmount(totalAmount);
    orderMessage.setSendTime(LocalDateTime.now());

    // 单向发送:无返回值,不等待响应
    rocketMQTemplate.sendOneWay(orderTopic, orderMessage);
    log.info("单向订单消息发送完成,订单编号:{},消息内容:{}", orderNo, JSON.toJSONString(orderMessage));

    // 记录消息状态(单向发送无法确认是否成功,仅记录发送操作)
    TOrderMessage orderMessageRecord = new TOrderMessage();
    orderMessageRecord.setOrderNo(orderNo);
    orderMessageRecord.setMessageStatus((byte) 0); // 0-待发送(无法确认是否成功)
    orderMessageRecord.setCreateTime(LocalDateTime.now());
    orderMessageRecord.setUpdateTime(LocalDateTime.now());
    orderMessageMapper.insert(orderMessageRecord);

    return orderNo;
}

2. Controller 新增接口

复制代码
@PostMapping("/create-oneway")
@Operation(summary = "创建订单(单向消息)", description = "创建订单并单向发送消息到RocketMQ")
public ResponseEntity<String> createOrderOneWay(
        @Parameter(description = "用户ID", required = true) @RequestParam Long userId,
        @Parameter(description = "商品ID", required = true) @RequestParam Long productId,
        @Parameter(description = "购买数量", required = true) @RequestParam Integer quantity,
        @Parameter(description = "商品单价", required = true) @RequestParam BigDecimal unitPrice) {
    String orderNo = orderService.createOrderOneWay(userId, productId, quantity, unitPrice);
    return ResponseEntity.ok("单向订单创建成功,订单编号:" + orderNo);
}

3.3 关键:顺序消息(保证消息消费顺序)

3.3.1 顺序消息原理

顺序消息是指消息的发送顺序与消费顺序一致,适用于需要严格顺序的场景(如订单状态变更:创建→支付→发货→完成)。

RocketMQ 实现顺序消息的核心逻辑:

  1. 发送端:将同一业务标识(如订单编号)的消息发送到同一个 Topic 的同一个 Queue
  2. 消费端:采用单线程消费同一个 Queue 的消息(或使用并发消费但确保同一业务标识的消息被同一线程处理)

顺序消息流程图:

3.3.2 顺序消息实战(订单状态变更)

1. 订单状态消息实体

复制代码
package com.ken.rocketmq.common.entity;

import lombok.Data;
import java.io.Serializable;

/**
 * 订单状态变更消息
 * @author ken
 */
@Data
public class OrderStatusMessage implements Serializable {
    private static final long serialVersionUID = 1L;

    /**
     * 订单编号
     */
    private String orderNo;

    /**
     * 订单状态:0-待支付,1-已支付,2-已发货,3-已完成,4-已取消
     */
    private Integer status;

    /**
     * 状态变更时间
     */
    private Long statusChangeTime;

    /**
     * 备注
     */
    private String remark;
}

2. 生产者:发送顺序消息

Service 层新增方法

复制代码
/**
 * 发送订单状态变更顺序消息
 * @param orderNo 订单编号
 * @param status 订单状态
 * @param remark 备注
 */
@Override
public void sendOrderStatusMessage(String orderNo, Integer status, String remark) {
    // 参数校验
    if (StringUtils.isEmpty(orderNo)) {
        throw new IllegalArgumentException("订单编号不能为空");
    }
    if (ObjectUtils.isEmpty(status)) {
        throw new IllegalArgumentException("订单状态不能为空");
    }

    // 构建顺序消息
    OrderStatusMessage statusMessage = new OrderStatusMessage();
    statusMessage.setOrderNo(orderNo);
    statusMessage.setStatus(status);
    statusMessage.setStatusChangeTime(System.currentTimeMillis());
    statusMessage.setRemark(remark);

    log.info("准备发送订单状态顺序消息,订单编号:{},状态:{},消息内容:{}",
            orderNo, status, JSON.toJSONString(statusMessage));

    // 顺序消息发送:根据订单编号哈希选择Queue(确保同一订单的消息进入同一个Queue)
    int queueIndex = Math.abs(orderNo.hashCode()) % 4; // 假设Topic有4个Queue
    Message<OrderStatusMessage> message = MessageBuilder.withPayload(statusMessage).build();

    // 发送顺序消息
    SendResult sendResult = rocketMQTemplate.send(orderStatusTopic, queueIndex, message);
    log.info("订单状态顺序消息发送成功,订单编号:{},Queue索引:{},消息ID:{}",
            orderNo, queueIndex, sendResult.getMsgId());
}

Controller 新增接口

复制代码
@PostMapping("/status/change")
@Operation(summary = "变更订单状态(顺序消息)", description = "变更订单状态并发送顺序消息")
public ResponseEntity<String> changeOrderStatus(
        @Parameter(description = "订单编号", required = true) @RequestParam String orderNo,
        @Parameter(description = "订单状态:0-待支付,1-已支付,2-已发货,3-已完成,4-已取消", required = true) @RequestParam Integer status,
        @Parameter(description = "备注") @RequestParam(required = false) String remark) {
    orderService.sendOrderStatusMessage(orderNo, status, remark);
    return ResponseEntity.ok("订单状态变更消息发送成功,订单编号:" + orderNo);
}

3. 消费者:顺序消费消息

消息监听类

复制代码
package com.ken.rocketmq.consumer.listener;

import com.alibaba.fastjson2.JSON;
import com.ken.rocketmq.common.entity.OrderStatusMessage;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;

/**
 * 订单状态顺序消息消费者
 * @author ken
 */
@Component
@Slf4j
@RocketMQMessageListener(
        topic = "${rocketmq.topic.order-status-topic:order_status_topic}",
        consumerGroup = "${rocketmq.consumer.group:order-status-consumer-group}",
        consumeMode = ConsumeMode.ORDERLY, // 顺序消费模式(必须设置)
        messageModel = MessageModel.CLUSTERING,
        consumeThreadMin = 4, // 消费线程数与Queue数一致
        consumeThreadMax = 4
)
public class OrderStatusOrderlyConsumer implements RocketMQListener<OrderStatusMessage> {

    @Override
    public void onMessage(OrderStatusMessage message) {
        log.info("收到订单状态顺序消息,开始处理,订单编号:{},状态:{},消息内容:{}",
                message.getOrderNo(), message.getStatus(), JSON.toJSONString(message));

        try {
            // 模拟业务处理(如更新订单状态)
            Thread.sleep(500); // 模拟处理耗时

            log.info("订单状态顺序消息处理成功,订单编号:{},状态:{}",
                    message.getOrderNo(), message.getStatus());
        } catch (Exception e) {
            log.error("订单状态顺序消息处理失败,订单编号:{},异常信息:{}",
                    message.getOrderNo(), e.getMessage(), e);
            // 顺序消息消费失败会阻塞当前Queue,需谨慎处理
            throw new RuntimeException("订单状态消息处理失败", e);
        }
    }
}
3.3.3 测试验证
  1. 手动创建 Topic order_status_topic,设置 4 个 Queue:

    sh bin/mqadmin updateTopic -n 127.0.0.1:9876 -t order_status_topic -r 4 -w 4

  2. 调用 /api/order/status/change 接口,对同一订单编号依次发送状态 0→1→2→3

  3. 查看消费者日志:确认消息按发送顺序消费

  4. 验证:同一订单的消息被分配到同一个 Queue,且按顺序处理

3.4 核心:事务消息(解决分布式事务问题)

3.4.1 事务消息原理

分布式事务场景中, RocketMQ 事务消息通过「两阶段提交」确保消息发送与本地事务的原子性:

  1. 第一阶段:生产者发送「半事务消息」到 Broker,Broker 存储消息但标记为「不可消费」
  2. 第二阶段
    • 若本地事务执行成功,生产者向 Broker 发送「提交消息」指令,Broker 标记消息为「可消费」
    • 若本地事务执行失败,生产者向 Broker 发送「回滚消息」指令,Broker 删除半事务消息
  3. 事务回查:若 Broker 长时间未收到第二阶段指令,会主动向生产者查询事务状态,生产者需根据本地事务结果响应提交 / 回滚

事务消息流程图:

3.4.2 事务消息实战(订单创建 + 库存扣减分布式事务)

1. 事务消息参数类

复制代码
package com.ken.rocketmq.producer.dto;

import lombok.Data;
import java.math.BigDecimal;

/**
 * 订单事务消息参数
 * @author ken
 */
@Data
public class OrderTransactionArgs {
    /**
     * 用户ID
     */
    private Long userId;
    
    /**
     * 商品ID
     */
    private Long productId;
    
    /**
     * 购买数量
     */
    private Integer quantity;
    
    /**
     * 商品单价
     */
    private BigDecimal unitPrice;
}

2. 事务消息监听器(续)

复制代码
            // 保存订单记录
            TOrder order = new TOrder();
            order.setOrderNo(orderNo);
            order.setUserId(userId);
            order.setProductId(productId);
            order.setQuantity(quantity);
            order.setTotalAmount(unitPrice.multiply(new BigDecimal(quantity)));
            order.setOrderStatus((byte) 0); // 0-待支付
            order.setCreateTime(LocalDateTime.now());
            order.setUpdateTime(LocalDateTime.now());
            orderMapper.insert(order);
            
            // 保存事务消息记录(用于回查)
            TOrderMessage orderMessage = new TOrderMessage();
            orderMessage.setOrderNo(orderNo);
            orderMessage.setMessageId(message.getHeaders().get("KEYS").toString()); // 消息唯一标识
            orderMessage.setMessageStatus((byte) 0); // 0-待确认
            orderMessage.setCreateTime(LocalDateTime.now());
            orderMessage.setUpdateTime(LocalDateTime.now());
            orderMessageMapper.insert(orderMessage);
            
            log.info("本地事务执行成功,订单编号:{}", orderNo);
            return RocketMQLocalTransactionState.COMMIT; // 提交事务消息
            
        } catch (Exception e) {
            log.error("本地事务执行失败,异常信息:{}", e.getMessage(), e);
            return RocketMQLocalTransactionState.ROLLBACK; // 回滚事务消息
        }
    }

    /**
     * 事务回查(Broker主动查询本地事务状态)
     * @param message 半事务消息
     * @return 事务状态
     */
    @Override
    public RocketMQLocalTransactionState checkLocalTransaction(Message message) {
        String messageId = message.getHeaders().get("KEYS").toString();
        log.info("执行事务回查,消息ID:{},消息内容:{}", messageId, JSON.toJSONString(message));
        
        try {
            // 根据消息ID查询本地事务记录
            TOrderMessage orderMessage = orderMessageMapper.selectOne(
                    new LambdaQueryWrapper<TOrderMessage>().eq(TOrderMessage::getMessageId, messageId));
            
            if (ObjectUtils.isEmpty(orderMessage)) {
                log.warn("事务回查失败:未找到消息记录,消息ID:{}", messageId);
                return RocketMQLocalTransactionState.ROLLBACK;
            }
            
            // 根据订单编号查询订单是否存在
            TOrder order = orderMapper.selectOne(
                    new LambdaQueryWrapper<TOrder>().eq(TOrder::getOrderNo, orderMessage.getOrderNo()));
            
            if (ObjectUtils.isEmpty(order)) {
                log.warn("事务回查:订单不存在,订单编号:{},消息ID:{}", orderMessage.getOrderNo(), messageId);
                return RocketMQLocalTransactionState.ROLLBACK;
            }
            
            // 订单存在则提交消息
            log.info("事务回查成功:订单存在,订单编号:{},消息ID:{},提交事务消息", orderMessage.getOrderNo(), messageId);
            return RocketMQLocalTransactionState.COMMIT;
            
        } catch (Exception e) {
            log.error("事务回查异常,消息ID:{},异常信息:{}", messageId, e.getMessage(), e);
            return RocketMQLocalTransactionState.UNKNOWN; // 暂不处理,等待下一次回查
        }
    }
}

3. 生产者 Service 层事务消息发送方法

复制代码
/**
 * 创建订单(事务消息)
 * @param transactionArgs 事务参数
 * @return 订单编号
 */
@Override
public String createOrderWithTransaction(OrderTransactionArgs transactionArgs) {
    // 参数校验
    if (ObjectUtils.isEmpty(transactionArgs)) {
        throw new IllegalArgumentException("事务参数不能为空");
    }
    if (ObjectUtils.isEmpty(transactionArgs.getUserId())) {
        throw new IllegalArgumentException("用户ID不能为空");
    }
    
    // 构建订单消息
    OrderMessage orderMessage = new OrderMessage();
    orderMessage.setUserId(transactionArgs.getUserId());
    orderMessage.setProductId(transactionArgs.getProductId());
    orderMessage.setQuantity(transactionArgs.getQuantity());
    orderMessage.setTotalAmount(transactionArgs.getUnitPrice().multiply(new BigDecimal(transactionArgs.getQuantity())));
    orderMessage.setSendTime(LocalDateTime.now());
    
    // 生成唯一消息ID(用于事务回查)
    String messageId = UUID.randomUUID().toString().replace("-", "");
    
    // 发送事务消息
    try {
        rocketMQTemplate.sendMessageInTransaction(
                "order-transaction-producer-group", // 事务生产者组
                "order_topic:transaction", // Topic+Tag
                MessageBuilder.withPayload(orderMessage)
                        .setHeader("KEYS", messageId) // 设置消息唯一标识
                        .build(),
                transactionArgs // 传递给本地事务的参数
        );
        log.info("事务消息发送成功,消息ID:{},订单参数:{}", messageId, JSON.toJSONString(transactionArgs));
        
        // 这里通过事务回查保障最终一致性,暂时返回消息ID关联的订单编号(实际可通过消息ID查询)
        return "TEMP_" + messageId.substring(0, 16);
        
    } catch (Exception e) {
        log.error("事务消息发送失败,参数:{},异常信息:{}", JSON.toJSONString(transactionArgs), e.getMessage(), e);
        throw new RuntimeException("订单创建失败:事务消息发送异常");
    }
}

4. 生产者 Controller 层接口

复制代码
@PostMapping("/create-transaction")
@Operation(summary = "创建订单(事务消息)", description = "基于RocketMQ事务消息创建订单,保证分布式事务一致性")
public ResponseEntity<String> createOrderWithTransaction(
        @Parameter(description = "订单事务参数", required = true) @RequestBody OrderTransactionArgs transactionArgs) {
    String orderNo = orderService.createOrderWithTransaction(transactionArgs);
    return ResponseEntity.ok("事务订单创建请求已接收,临时订单标识:" + orderNo);
}

5. 消费者事务消息处理

复制代码
package com.ken.rocketmq.consumer.listener;

import com.alibaba.fastjson2.JSON;
import com.ken.rocketmq.common.entity.OrderMessage;
import com.ken.rocketmq.consumer.service.InventoryService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;

/**
 * 订单事务消息消费者(库存扣减)
 * @author ken
 */
@Component
@Slf4j
@RequiredArgsConstructor
@RocketMQMessageListener(
        topic = "order_topic",
        selectorExpression = "transaction", // 只消费Tag=transaction的消息
        consumerGroup = "inventory-transaction-consumer-group"
)
public class OrderTransactionMessageConsumer implements RocketMQListener<OrderMessage> {

    private final InventoryService inventoryService;

    @Override
    public void onMessage(OrderMessage message) {
        log.info("收到事务订单消息,开始处理库存扣减,消息内容:{}", JSON.toJSONString(message));
        try {
            // 库存扣减逻辑与普通消息一致
            inventoryService.deductInventory(message);
            log.info("事务订单消息处理成功,订单编号:{}", message.getOrderNo());
        } catch (Exception e) {
            log.error("事务订单消息处理失败,订单编号:{},异常信息:{}", message.getOrderNo(), e.getMessage(), e);
            throw new RuntimeException("库存扣减失败,触发消息重试");
        }
    }
}

3.5 实用:延迟消息(定时任务场景)

3.5.1 延迟消息原理

RocketMQ 延迟消息通过「延迟级别」实现,不支持任意时间延迟,而是预定义了 18 个延迟级别:

复制代码
1=1s,2=5s,3=10s,4=30s,5=1m,6=2m,7=3m,8=4m,9=5m,10=6m,11=7m,12=8m,13=9m,14=10m,15=20m,16=30m,17=1h,18=2h

延迟消息发送时指定级别,Broker 会将消息暂存到延迟队列,到达时间后再投递到目标 Topic。

3.5.2 延迟消息实战(订单超时取消)

1. 生产者发送延迟消息

复制代码
/**
 * 发送订单超时取消延迟消息
 * @param orderNo 订单编号
 * @param delayLevel 延迟级别(1-18)
 */
@Override
public void sendOrderDelayCancelMessage(String orderNo, int delayLevel) {
    // 参数校验
    if (StringUtils.isEmpty(orderNo)) {
        throw new IllegalArgumentException("订单编号不能为空");
    }
    if (delayLevel < 1 || delayLevel > 18) {
        throw new IllegalArgumentException("延迟级别必须在1-18之间");
    }
    
    // 构建延迟消息
    OrderMessage delayMessage = new OrderMessage();
    delayMessage.setOrderNo(orderNo);
    delayMessage.setSendTime(LocalDateTime.now());
    
    log.info("准备发送订单超时取消延迟消息,订单编号:{},延迟级别:{}", orderNo, delayLevel);
    
    // 发送延迟消息
    Message<OrderMessage> message = MessageBuilder.withPayload(delayMessage)
            .setHeader(RocketMQHeaders.DELAY_LEVEL, delayLevel) // 设置延迟级别
            .build();
    
    rocketMQTemplate.send("order_delay_topic", message);
    log.info("订单超时取消延迟消息发送成功,订单编号:{},延迟级别:{}", orderNo, delayLevel);
}

2. 消费者处理延迟消息

java

运行

复制代码
package com.ken.rocketmq.consumer.listener;

import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.ken.rocketmq.common.entity.OrderMessage;
import com.ken.rocketmq.consumer.entity.TOrder;
import com.ken.rocketmq.consumer.mapper.TOrderMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;

import java.time.LocalDateTime;

/**
 * 订单超时取消延迟消息消费者
 * @author ken
 */
@Component
@Slf4j
@RequiredArgsConstructor
@RocketMQMessageListener(
        topic = "order_delay_topic",
        consumerGroup = "order-delay-consumer-group"
)
public class OrderDelayCancelConsumer implements RocketMQListener<OrderMessage> {

    private final TOrderMapper orderMapper;

    @Override
    public void onMessage(OrderMessage message) {
        String orderNo = message.getOrderNo();
        log.info("收到订单超时取消延迟消息,开始处理,订单编号:{}", orderNo);
        
        try {
            // 查询订单状态(待支付才取消)
            TOrder order = orderMapper.selectOne(
                    new LambdaQueryWrapper<TOrder>().eq(TOrder::getOrderNo, orderNo));
            
            if (ObjectUtils.isEmpty(order)) {
                log.warn("订单不存在,无需取消,订单编号:{}", orderNo);
                return;
            }
            
            if (order.getOrderStatus() != 0) { // 非待支付状态不处理
                log.info("订单已支付或取消,无需处理,订单编号:{},当前状态:{}", orderNo, order.getOrderStatus());
                return;
            }
            
            // 更新订单状态为已取消
            LambdaUpdateWrapper<TOrder> updateWrapper = new LambdaUpdateWrapper<TOrder>()
                    .eq(TOrder::getOrderNo, orderNo)
                    .eq(TOrder::getOrderStatus, 0) // 确保是待支付状态
                    .set(TOrder::getOrderStatus, (byte) 2) // 2-已取消
                    .set(TOrder::getUpdateTime, LocalDateTime.now());
            
            int updateCount = orderMapper.update(null, updateWrapper);
            if (updateCount > 0) {
                log.info("订单超时取消成功,订单编号:{}", orderNo);
            } else {
                log.warn("订单状态已变更,取消失败,订单编号:{}", orderNo);
            }
            
        } catch (Exception e) {
            log.error("订单超时取消处理失败,订单编号:{},异常信息:{}", orderNo, e.getMessage(), e);
            throw new RuntimeException("订单取消失败,触发消息重试");
        }
    }
}

3.6 高效:批量消息(提升吞吐量)

3.6.1 批量消息原理

批量消息允许将多条消息打包发送,减少网络交互次数,提升吞吐量。RocketMQ 要求批量消息总大小不超过 4MB,且必须发送到同一个 Topic 和 Queue。

3.6.2 批量消息实战

1. 生产者发送批量消息

复制代码
/**
 * 批量创建订单并发送消息
 * @param orderList 订单列表
 * @return 成功创建的订单编号列表
 */
@Override
public List<String> batchCreateOrder(List<OrderTransactionArgs> orderList) {
    // 参数校验
    if (CollectionUtils.isEmpty(orderList)) {
        throw new IllegalArgumentException("订单列表不能为空");
    }
    if (orderList.size() > 100) { // 限制单次批量大小
        throw new IllegalArgumentException("批量订单数量不能超过100");
    }
    
    List<String> successOrderNos = Lists.newArrayList();
    List<Message<OrderMessage>> messageList = Lists.newArrayList();
    
    // 1. 批量创建订单
    try (SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
        TOrderMapper batchOrderMapper = sqlSession.getMapper(TOrderMapper.class);
        TOrderMessageMapper batchMessageMapper = sqlSession.getMapper(TOrderMessageMapper.class);
        
        for (OrderTransactionArgs args : orderList) {
            // 参数校验
            if (ObjectUtils.isEmpty(args.getUserId()) || ObjectUtils.isEmpty(args.getProductId())
                    || ObjectUtils.isEmpty(args.getQuantity()) || args.getQuantity() <= 0) {
                log.warn("批量订单参数无效,跳过:{}", JSON.toJSONString(args));
                continue;
            }
            
            // 生成订单编号
            String orderNo = UUID.randomUUID().toString().replace("-", "").substring(0, 20);
            
            // 保存订单
            TOrder order = new TOrder();
            order.setOrderNo(orderNo);
            order.setUserId(args.getUserId());
            order.setProductId(args.getProductId());
            order.setQuantity(args.getQuantity());
            order.setTotalAmount(args.getUnitPrice().multiply(new BigDecimal(args.getQuantity())));
            order.setOrderStatus((byte) 0);
            order.setCreateTime(LocalDateTime.now());
            order.setUpdateTime(LocalDateTime.now());
            batchOrderMapper.insert(order);
            
            // 保存消息记录
            TOrderMessage orderMessage = new TOrderMessage();
            orderMessage.setOrderNo(orderNo);
            orderMessage.setMessageStatus((byte) 0);
            orderMessage.setCreateTime(LocalDateTime.now());
            orderMessage.setUpdateTime(LocalDateTime.now());
            batchMessageMapper.insert(orderMessage);
            
            // 构建消息
            OrderMessage messageBody = new OrderMessage();
            messageBody.setOrderNo(orderNo);
            messageBody.setUserId(args.getUserId());
            messageBody.setProductId(args.getProductId());
            messageBody.setQuantity(args.getQuantity());
            messageBody.setTotalAmount(args.getTotalAmount());
            messageBody.setSendTime(LocalDateTime.now());
            
            messageList.add(MessageBuilder.withPayload(messageBody).build());
            successOrderNos.add(orderNo);
        }
        
        sqlSession.commit();
        log.info("批量订单创建成功,数量:{}", successOrderNos.size());
        
    } catch (Exception e) {
        log.error("批量订单创建失败,异常信息:{}", e.getMessage(), e);
        throw new RuntimeException("批量订单创建失败");
    }
    
    // 2. 发送批量消息
    if (!CollectionUtils.isEmpty(messageList)) {
        try {
            // 分割超过4MB的消息列表(RocketMQ限制)
            List<List<Message<OrderMessage>>> splitList = rocketMQTemplate.splitMessages(messageList);
            for (List<Message<OrderMessage>> subList : splitList) {
                rocketMQTemplate.syncSend("order_batch_topic", subList);
            }
            log.info("批量订单消息发送成功,消息数量:{}", messageList.size());
        } catch (Exception e) {
            log.error("批量消息发送失败,消息数量:{},异常信息:{}", messageList.size(), e.getMessage(), e);
            throw new RuntimeException("批量消息发送失败");
        }
    }
    
    return successOrderNos;
}

2. 消费者处理批量消息

复制代码
package com.ken.rocketmq.consumer.listener;

import com.alibaba.fastjson2.JSON;
import com.ken.rocketmq.common.entity.OrderMessage;
import com.ken.rocketmq.consumer.service.InventoryService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * 批量订单消息消费者
 * @author ken
 */
@Component
@Slf4j
@RequiredArgsConstructor
@RocketMQMessageListener(
        topic = "order_batch_topic",
        consumerGroup = "inventory-batch-consumer-group",
        consumeMessageBatchMaxSize = 32 // 批量消费最大条数
)
public class OrderBatchMessageConsumer implements RocketMQListener<List<OrderMessage>> {

    private final InventoryService inventoryService;

    @Override
    public void onMessage(List<OrderMessage> messages) {
        log.info("收到批量订单消息,数量:{},消息列表:{}", messages.size(), JSON.toJSONString(messages));
        
        try {
            // 批量处理库存扣减
            for (OrderMessage message : messages) {
                try {
                    inventoryService.deductInventory(message);
                } catch (Exception e) {
                    log.error("批量消息中单条处理失败,订单编号:{},异常信息:{}", message.getOrderNo(), e.getMessage(), e);
                    // 单条失败不影响整体,记录异常后继续处理下一条
                }
            }
            log.info("批量订单消息处理完成,成功数量:{}", messages.size());
            
        } catch (Exception e) {
            log.error("批量订单消息处理失败,数量:{},异常信息:{}", messages.size(), e.getMessage(), e);
            throw new RuntimeException("批量消息处理失败,触发重试");
        }
    }
}

3.7 精准:过滤消息(按 Tag/SQL 过滤)

3.7.1 消息过滤原理

RocketMQ 支持两种过滤方式:

  • Tag 过滤:基于消息 Tag 进行简单过滤(高效,推荐)
  • SQL92 过滤:基于消息属性进行复杂条件过滤(需 Broker 开启支持)
3.7.2 Tag 过滤实战

1. 生产者发送带 Tag 的消息

复制代码
/**
 * 发送带Tag的订单消息
 * @param orderNo 订单编号
 * @param tag 消息Tag(如"CREATE","PAY","CANCEL")
 */
@Override
public void sendOrderMessageWithTag(String orderNo, String tag) {
    if (StringUtils.isEmpty(orderNo)) {
        throw new IllegalArgumentException("订单编号不能为空");
    }
    if (StringUtils.isEmpty(tag)) {
        throw new IllegalArgumentException("消息Tag不能为空");
    }
    
    // 查询订单信息
    TOrder order = orderMapper.selectOne(new LambdaQueryWrapper<TOrder>().eq(TOrder::getOrderNo, orderNo));
    if (ObjectUtils.isEmpty(order)) {
        throw new RuntimeException("订单不存在");
    }
    
    // 构建消息
    OrderMessage messageBody = new OrderMessage();
    messageBody.setOrderNo(orderNo);
    messageBody.setUserId(order.getUserId());
    messageBody.setProductId(order.getProductId());
    messageBody.setQuantity(order.getQuantity());
    messageBody.setTotalAmount(order.getTotalAmount());
    messageBody.setSendTime(LocalDateTime.now());
    
    // 发送带Tag的消息(Topic:Tag格式)
    rocketMQTemplate.syncSend("order_tag_topic:" + tag, messageBody);
    log.info("带Tag的订单消息发送成功,订单编号:{},Tag:{}", orderNo, tag);
}

2. 消费者按 Tag 过滤

复制代码
// 消费Tag=CREATE的消息
@Component
@RocketMQMessageListener(
        topic = "order_tag_topic",
        selectorExpression = "CREATE", // 只消费CREATE Tag
        consumerGroup = "inventory-create-consumer-group"
)
public class OrderCreateTagConsumer implements RocketMQListener<OrderMessage> {
    // 消费逻辑...
}

// 消费Tag=PAY或CANCEL的消息
@Component
@RocketMQMessageListener(
        topic = "order_tag_topic",
        selectorExpression = "PAY || CANCEL", // 消费多个Tag
        consumerGroup = "order-pay-cancel-consumer-group"
)
public class OrderPayCancelTagConsumer implements RocketMQListener<OrderMessage> {
    // 消费逻辑...
}
3.7.3 SQL 过滤实战

1. Broker 开启 SQL 过滤 修改 Broker 配置文件broker.conf

复制代码
enablePropertyFilter=true # 开启属性过滤

2. 生产者发送带属性的消息

复制代码
/**
 * 发送带属性的订单消息(支持SQL过滤)
 * @param orderNo 订单编号
 */
@Override
public void sendOrderMessageWithProperties(String orderNo) {
    TOrder order = orderMapper.selectOne(new LambdaQueryWrapper<TOrder>().eq(TOrder::getOrderNo, orderNo));
    if (ObjectUtils.isEmpty(order)) {
        throw new RuntimeException("订单不存在");
    }
    
    OrderMessage messageBody = new OrderMessage();
    // 构建消息体...
    
    // 发送带属性的消息
    Message<OrderMessage> message = MessageBuilder.withPayload(messageBody)
            .setHeader("userId", order.getUserId()) // 用户ID属性
            .setHeader("amount", order.getTotalAmount()) // 订单金额属性
            .setHeader("createTime", order.getCreateTime().toString()) // 创建时间属性
            .build();
    
    rocketMQTemplate.syncSend("order_sql_topic", message);
    log.info("带属性的订单消息发送成功,订单编号:{},用户ID:{},金额:{}", 
            orderNo, order.getUserId(), order.getTotalAmount());
}

3. 消费者按 SQL 过滤

复制代码
@Component
@Slf4j
@RocketMQMessageListener(
        topic = "order_sql_topic",
        selectorType = SelectorType.SQL92, // 指定SQL过滤
        selectorExpression = "userId > 1000 AND amount > 100", // SQL条件
        consumerGroup = "order-sql-consumer-group"
)
public class OrderSQLFilterConsumer implements RocketMQListener<OrderMessage> {
    @Override
    public void onMessage(OrderMessage message) {
        log.info("收到SQL过滤后的订单消息,订单编号:{},用户ID:{},金额:{}",
                message.getOrderNo(), message.getUserId(), message.getTotalAmount());
        // 消费逻辑...
    }
}

3.8 容错:死信队列(处理消费失败的消息)

3.8.1 死信队列原理

当消息消费失败次数达到重试上限(默认 16 次),RocketMQ 会将消息移入「死信队列」(DLQ)。死信队列命名规则:%DLQ%+消费者组名,死信消息不会被自动重试,需人工干预处理。

3.8.2 死信队列实战

1. 配置消费者重试次数

复制代码
rocketmq:
  consumer:
    retry-times-when-consume-failed: 3 # 消费失败重试次数(默认16次,测试时减小)

2. 死信消息监听

复制代码
@Component
@Slf4j
@RocketMQMessageListener(
        topic = "%DLQ%inventory-consumer-group", // 死信队列名称
        consumerGroup = "dlq-consumer-group"
)
public class OrderDLQConsumer implements RocketMQListener<OrderMessage> {
    @Override
    public void onMessage(OrderMessage message) {
        log.error("收到死信队列消息,订单编号:{},消息内容:{}", message.getOrderNo(), JSON.toJSONString(message));
        // 死信消息处理逻辑:记录日志、告警通知、人工介入等
    }
}

四、微服务集成场景:RocketMQ 典型应用

4.1 服务解耦:订单 - 库存 - 支付流程

通过 RocketMQ 实现订单、库存、支付服务的解耦,流程如下:

  1. 订单服务创建订单后发送消息到 order_topic
  2. 库存服务消费消息扣减库存,发送消息到 inventory_topic
  3. 支付服务消费消息处理支付,发送消息到 pay_topic
  4. 订单服务消费支付结果消息更新订单状态

4.2 异步通信:用户注册后发送短信 / 邮件

用户服务注册成功后发送异步消息,通知短信 / 邮件服务发送通知,无需等待第三方服务响应,提升接口响应速度。

4.3 削峰填谷:秒杀场景流量控制

秒杀活动中,大量请求涌入时,先将请求写入 RocketMQ,消费端按能力匀速处理,避免系统过载。

五、性能优化:RocketMQ 调优实战

5.1 生产者优化

  • 批量发送:使用批量消息减少网络请求
  • 异步发送:非核心场景使用异步发送提升吞吐量
  • 压缩消息:开启消息压缩(默认 4KB 以上压缩)
  • 重试机制:合理设置重试次数(避免消息重复)

5.2 消费者优化

  • 并发消费 :调整消费线程数(consumeThreadMin/max
  • 批量消费 :设置 consumeMessageBatchMaxSize 提升效率
  • 消息堆积处理:临时增加消费线程或扩容消费者节点

5.3 Broker 优化

  • 存储优化:使用 SSD 硬盘存储消息
  • 刷盘策略 :生产环境使用 ASYNC_FLUSH 平衡性能与可靠性
  • 扩容 Broker:增加 Broker 节点分担负载

六、问题排查:常见故障解决方案

6.1 消息丢失

  • 生产者:开启事务消息或同步发送并确认结果
  • Broker:开启主从同步,确保消息持久化
  • 消费者:消费完成后手动提交偏移量

6.2 消息重复

  • 消费端实现幂等性(基于订单编号等唯一标识)
  • 使用数据库唯一索引或分布式锁防止重复处理

6.3 消息堆积

  • 查看 Broker 监控,确认消费速度是否低于生产速度
  • 临时扩容消费者节点或增加消费线程
  • 检查消费端是否有慢查询或资源瓶颈

七、总结

Spring Cloud Alibaba RocketMQ 为微服务架构提供了强大的消息通信能力,通过本文的实战案例,我们掌握了消息发送 / 消费、顺序消息、事务消息、延迟消息等核心功能,以及性能优化和问题排查技巧。在实际项目中,需结合业务场景选择合适的消息类型,遵循「解耦、可靠、高效」的原则,充分发挥 RocketMQ 的优势,构建稳定、高性能的微服务系统。

相关推荐
共绩算力1 小时前
【共绩 AI 小课堂】Class 5 Transformer架构深度解析:从《Attention Is All You Need》论文到现代大模型
人工智能·架构·transformer·共绩算力
canonical_entropy1 小时前
模型驱动架构的数学内核:统一生成与演化的 Y = F(X) ⊕ Delta 不变式
数学·设计模式·架构
記億揺晃着的那天4 小时前
从单体到微服务:如何拆分
java·微服务·ddd·devops·系统拆分
拾忆,想起4 小时前
Dubbo超时问题排查与调优指南:从根因到解决方案
服务器·开发语言·网络·微服务·架构·php·dubbo
shuidaoyuxing4 小时前
对 微服务 进行一次系统化、结构化的全面讲解
微服务·云原生·架构
k***19510 小时前
自动驾驶---E2E架构演进
人工智能·架构·自动驾驶
Mr_sun.11 小时前
Day11——微服务高级
微服务·云原生·架构
小毅&Nora11 小时前
【AI微服务】【Spring AI Alibaba】 ① 技术内核全解析:架构、组件与无缝扩展新模型能力
人工智能·微服务·架构
q***766611 小时前
SDN架构详解
架构