作为微服务架构中的核心组件,消息队列承担着解耦、削峰、异步通信等关键职责。而 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-rocketmq 或 rocketmq-spring-boot-starter 实现自动配置,底层逻辑如下:
- 自动配置类 :
RocketMQAutoConfiguration负责初始化核心组件(RocketMQTemplate、DefaultMQProducer、DefaultMQPushConsumer等) - 模板类封装 :
RocketMQTemplate封装了消息发送的所有操作(同步、异步、单向、事务等),简化 API 调用 - 注解驱动 :通过
@RocketMQMessageListener注解声明消费者,自动注册到 RocketMQ 集群 - 配置绑定 :通过
application.yml配置 RocketMQ 连接信息(NameServer 地址、生产者组、消费者组等),无需手动创建连接
集成流程图:

1.3 关键依赖说明
Spring Cloud Alibaba RocketMQ 集成的核心依赖分为两类:
-
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> -
核心组件依赖(需手动配置,灵活度高):
<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)
部署步骤:
-
下载安装包:
官网下载(推荐)
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 -
修改配置文件(优化单机性能):
修改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(开发环境) -
启动 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
-
启动 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
-
关闭服务命令(后续备用):
关闭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 依赖 :与生产者模块一致,仅需修改 artifactId 和 name:
<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 测试验证
- 启动 Nacos、RocketMQ 服务端
- 启动生产者、消费者服务
- 访问 Swagger 地址:http://localhost:8081/swagger-ui.html
- 调用
/api/order/create接口,传入参数(如 userId=1001,productId=2001,quantity=2,unitPrice=99.9) - 查看生产者日志:确认订单创建成功,消息发送成功
- 查看消费者日志:确认消息接收成功,库存扣减成功
- 查看数据库:
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 实现顺序消息的核心逻辑:
- 发送端:将同一业务标识(如订单编号)的消息发送到同一个 Topic 的同一个 Queue
- 消费端:采用单线程消费同一个 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 测试验证
-
手动创建 Topic
order_status_topic,设置 4 个 Queue:sh bin/mqadmin updateTopic -n 127.0.0.1:9876 -t order_status_topic -r 4 -w 4
-
调用
/api/order/status/change接口,对同一订单编号依次发送状态 0→1→2→3 -
查看消费者日志:确认消息按发送顺序消费
-
验证:同一订单的消息被分配到同一个 Queue,且按顺序处理
3.4 核心:事务消息(解决分布式事务问题)
3.4.1 事务消息原理
分布式事务场景中, RocketMQ 事务消息通过「两阶段提交」确保消息发送与本地事务的原子性:
- 第一阶段:生产者发送「半事务消息」到 Broker,Broker 存储消息但标记为「不可消费」
- 第二阶段 :
- 若本地事务执行成功,生产者向 Broker 发送「提交消息」指令,Broker 标记消息为「可消费」
- 若本地事务执行失败,生产者向 Broker 发送「回滚消息」指令,Broker 删除半事务消息
- 事务回查:若 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 实现订单、库存、支付服务的解耦,流程如下:
- 订单服务创建订单后发送消息到
order_topic - 库存服务消费消息扣减库存,发送消息到
inventory_topic - 支付服务消费消息处理支付,发送消息到
pay_topic - 订单服务消费支付结果消息更新订单状态
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 的优势,构建稳定、高性能的微服务系统。