引言
RocketMQ是阿里巴巴开源的一款分布式消息中间件,具有高吞吐量、高可用性、低延迟的特点,广泛应用于电商、金融、物联网等领域的消息处理场景。相较于Kafka和RabbitMQ,RocketMQ在金融级场景下的可靠性表现尤为突出,支持事务消息、顺序消息、延时消息等高级特性。
本文将从零开始,详细介绍RocketMQ的核心概念、环境搭建、与Spring Boot项目的完整整合过程,帮助开发者快速掌握这一强大的消息中间件。
RocketMQ核心概念解析
1. 消息模型组件
Producer(生产者)
- 负责发送消息到RocketMQ服务器
- 支持同步发送、异步发送、单向发送三种方式
- 可以按消息类型分为普通生产者、事务生产者
Consumer(消费者)
- 从RocketMQ服务器拉取并消费消息
- 支持推模式(Push)和拉模式(Pull)两种消费模型
- 分为集群消费和广播消费两种模式
Topic(主题)
- 消息的逻辑分类单元
- 生产者发送消息到指定Topic
- 消费者订阅Topic来消费消息
- Topic与Queue是多对一关系
Message Queue(消息队列)
- Topic的物理存储单元
- 一个Topic可以包含多个Queue
- 实现消息的分片存储和负载均衡
NameServer(名称服务器)
- 提供轻量级的路由注册发现服务
- 管理Broker的路由信息
- 无状态设计,集群部署简单
Broker(代理服务器)
- 消息存储和转发的核心组件
- 负责消息的接收、存储、投递
- 支持主从复制和故障切换
2. 消息类型
普通消息
- 无特殊功能要求的常规消息
- 支持三种发送方式:同步、异步、单向
顺序消息
- 保证消息的消费顺序与发送顺序一致
- 分为全局顺序和分区顺序
- 通过Message Queue实现顺序保证
事务消息
- 保证消息发送与本地事务的最终一致性
- 采用两阶段提交协议
- 适用于分布式事务场景
延时消息
- 消息发送后延迟一定时间才可被消费
- 支持特定的延时级别(如1s、5s、10s等)
- 适用于订单超时取消等场景
Spring Boot整合实现
1. 项目依赖配置
Maven配置
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- Spring Boot父项目 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.15</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>rocketmq-springboot-demo</artifactId>
<version>1.0.0</version>
<name>rocketmq-springboot-demo</name>
<description>RocketMQ Spring Boot整合示例</description>
<properties>
<java.version>1.8</java.version>
<rocketmq-spring-boot-starter.version>2.3.3</rocketmq-spring-boot-starter.version>
</properties>
<dependencies>
<!-- Spring Boot Web依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- RocketMQ Spring Boot Starter -->
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>${rocketmq-spring-boot-starter.version}</version>
</dependency>
<!-- Lombok(简化代码) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- JSON处理 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.43</version>
</dependency>
<!-- 测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Gradle配置
gradle
plugins {
id 'org.springframework.boot' version '2.7.15'
id 'io.spring.dependency-management' version '1.0.15.RELEASE'
id 'java'
}
group = 'com.example'
version = '1.0.0'
sourceCompatibility = '1.8'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.apache.rocketmq:rocketmq-spring-boot-starter:2.3.3'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
implementation 'com.alibaba:fastjson:2.0.43'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
2. 配置文件
application.yml配置
yaml
server:
port: 8080
spring:
application:
name: rocketmq-springboot-demo
# RocketMQ配置
rocketmq:
# NameServer地址,多个地址用分号分隔
name-server: 127.0.0.1:9876
# 生产者配置
producer:
# 生产者组名
group: demo-producer-group
# 发送消息超时时间(毫秒)
send-message-timeout: 3000
# 消息体超过该值则启用压缩(字节)
compress-message-body-threshold: 4096
# 最大消息大小(字节,默认4M)
max-message-size: 4194304
# 同步发送失败重试次数
retry-times-when-send-failed: 2
# 异步发送失败重试次数
retry-times-when-send-async-failed: 0
# 发送失败时是否尝试重试到其他Broker
retry-next-server: true
# 消费者配置
consumer:
# 消费者组名
group: demo-consumer-group
# 最小消费线程数
consume-thread-min: 5
# 最大消费线程数
consume-thread-max: 20
# 消息消费失败重试次数(-1表示无限重试,16次后进入死信队列)
max-reconsume-times: 3
# 拉取消息的间隔时间(毫秒)
pull-batch-size: 32
application.properties配置
properties
server.port=8080
spring.application.name=rocketmq-springboot-demo
# RocketMQ NameServer地址
rocketmq.name-server=127.0.0.1:9876
# 生产者配置
rocketmq.producer.group=demo-producer-group
rocketmq.producer.send-message-timeout=3000
rocketmq.producer.retry-times-when-send-failed=2
# 消费者配置
rocketmq.consumer.group=demo-consumer-group
rocketmq.consumer.consume-thread-min=5
rocketmq.consumer.consume-thread-max=20
rocketmq.consumer.max-reconsume-times=3
3. 版本兼容性说明
| RocketMQ版本 | Spring Boot版本 | rocketmq-spring-boot-starter版本 |
|---|---|---|
| 5.3.x | 2.7.x, 3.x | 2.3.3+ |
| 5.2.x | 2.6.x, 2.7.x | 2.2.4 - 2.3.2 |
| 5.1.x | 2.5.x, 2.6.x | 2.2.0 - 2.2.3 |
| 4.9.x | 2.3.x - 2.7.x | 2.1.x - 2.2.x |
注意:
- Spring Boot 3.x需要使用JDK 17+
- 推荐使用稳定的版本组合进行生产部署
- 升级前务必在测试环境验证兼容性
生产者实现
1. 同步发送
同步发送是最可靠的发送方式,发送后会等待Broker的响应,适用于对可靠性要求高的场景。
java
package com.example.rocketmq.producer;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import java.util.UUID;
/**
* 同步消息发送服务
*
* @author example
* @date 2024-01-01
*/
@Slf4j
@Service
public class SyncMessageProducer {
@Autowired
private RocketMQTemplate rocketMQTemplate;
/**
* 发送简单字符串消息(同步方式)
*
* @param topic 主题名称
* @param message 消息内容
* @return 发送结果
*/
public SendResult sendSimpleMessage(String topic, String message) {
log.info("开始发送同步消息,topic: {}, message: {}", topic, message);
try {
// 使用convertAndSend方法直接发送
SendResult sendResult = rocketMQTemplate.syncSend(topic, message);
log.info("同步消息发送成功,topic: {}, messageId: {}, sendStatus: {}",
topic, sendResult.getMsgId(), sendResult.getSendStatus());
return sendResult;
} catch (Exception e) {
log.error("同步消息发送失败,topic: {}, message: {}", topic, message, e);
throw new RuntimeException("消息发送失败", e);
}
}
/**
* 发送带Tag的消息(同步方式)
* Topic和Tag使用冒号分隔:topic:tag
*
* @param topic 主题名称
* @param tag 消息标签
* @param message 消息内容
* @return 发送结果
*/
public SendResult sendTagMessage(String topic, String tag, String message) {
log.info("开始发送带Tag的同步消息,topic: {}, tag: {}, message: {}", topic, tag, message);
try {
// 使用topic:tag格式发送
String destination = topic + ":" + tag;
SendResult sendResult = rocketMQTemplate.syncSend(destination, message);
log.info("带Tag消息发送成功,topic: {}, tag: {}, messageId: {}",
topic, tag, sendResult.getMsgId());
return sendResult;
} catch (Exception e) {
log.error("带Tag消息发送失败,topic: {}, tag: {}", topic, tag, e);
throw new RuntimeException("消息发送失败", e);
}
}
/**
* 发送对象消息(同步方式)
* 消息会自动序列化为JSON格式
*
* @param topic 主题名称
* @param object 消息对象
* @return 发送结果
*/
public SendResult sendObjectMessage(String topic, Object object) {
log.info("开始发送对象消息,topic: {}, object: {}", topic, object);
try {
SendResult sendResult = rocketMQTemplate.syncSend(topic, object);
log.info("对象消息发送成功,topic: {}, messageId: {}",
topic, sendResult.getMsgId());
return sendResult;
} catch (Exception e) {
log.error("对象消息发送失败,topic: {}, object: {}", topic, object, e);
throw new RuntimeException("消息发送失败", e);
}
}
/**
* 发送带Message的消息(同步方式)
* 可以设置消息头、消息属性等
*
* @param topic 主题名称
* @param message 消息内容
* @return 发送结果
*/
public SendResult sendMessageWithHeader(String topic, String message) {
log.info("开始发送带Header的同步消息,topic: {}, message: {}", topic, message);
try {
// 构建Spring Message对象
Message<String> springMessage = MessageBuilder
.withPayload(message)
.setHeader("header-key", "header-value")
.setHeader("message-id", UUID.randomUUID().toString())
.build();
SendResult sendResult = rocketMQTemplate.syncSend(topic, springMessage);
log.info("带Header消息发送成功,topic: {}, messageId: {}",
topic, sendResult.getMsgId());
return sendResult;
} catch (Exception e) {
log.error("带Header消息发送失败,topic: {}, message: {}", topic, message, e);
throw new RuntimeException("消息发送失败", e);
}
}
/**
* 发送带超时时间的同步消息
*
* @param topic 主题名称
* @param message 消息内容
* @param timeout 超时时间(毫秒)
* @return 发送结果
*/
public SendResult sendWithTimeout(String topic, String message, long timeout) {
log.info("开始发送带超时时间的同步消息,topic: {}, timeout: {}ms, message: {}",
topic, timeout, message);
try {
SendResult sendResult = rocketMQTemplate.syncSend(topic, message, timeout);
log.info("带超时时间消息发送成功,topic: {}, messageId: {}",
topic, sendResult.getMsgId());
return sendResult;
} catch (Exception e) {
log.error("带超时时间消息发送失败,topic: {}, timeout: {}ms", topic, timeout, e);
throw new RuntimeException("消息发送失败", e);
}
}
}
2. 异步发送
异步发送不会阻塞当前线程,通过回调函数处理发送结果,适用于高吞吐量场景。
java
package com.example.rocketmq.producer;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* 异步消息发送服务
*
* @author example
* @date 2024-01-01
*/
@Slf4j
@Service
public class AsyncMessageProducer {
@Autowired
private RocketMQTemplate rocketMQTemplate;
/**
* 发送异步消息
*
* @param topic 主题名称
* @param message 消息内容
*/
public void sendAsyncMessage(String topic, String message) {
log.info("开始发送异步消息,topic: {}, message: {}", topic, message);
rocketMQTemplate.asyncSend(topic, message, new SendCallback() {
/**
* 发送成功的回调
*/
@Override
public void onSuccess(SendResult sendResult) {
log.info("异步消息发送成功,topic: {}, messageId: {}, sendStatus: {}",
topic, sendResult.getMsgId(), sendResult.getSendStatus());
// 这里可以执行发送成功后的业务逻辑
// 例如:记录日志、更新数据库等
}
/**
* 发送失败的回调
*/
@Override
public void onException(Throwable e) {
log.error("异步消息发送失败,topic: {}, message: {}", topic, message, e);
// 这里可以执行失败后的补偿逻辑
// 例如:重试、记录到数据库、发送告警等
}
});
}
/**
* 发送带超时时间的异步消息
*
* @param topic 主题名称
* @param message 消息内容
* @param timeout 超时时间(毫秒)
*/
public void sendAsyncWithTimeout(String topic, String message, long timeout) {
log.info("开始发送带超时时间的异步消息,topic: {}, timeout: {}ms, message: {}",
topic, timeout, message);
rocketMQTemplate.asyncSend(topic, message, new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
log.info("带超时时间异步消息发送成功,topic: {}, messageId: {}",
topic, sendResult.getMsgId());
}
@Override
public void onException(Throwable e) {
log.error("带超时时间异步消息发送失败,topic: {}, timeout: {}ms",
topic, timeout, e);
}
}, timeout);
}
/**
* 发送异步消息并等待结果(用于测试场景)
*
* @param topic 主题名称
* @param message 消息内容
* @throws InterruptedException 线程中断异常
*/
public void sendAsyncAndWait(String topic, String message) throws InterruptedException {
log.info("开始发送异步消息并等待结果,topic: {}, message: {}", topic, message);
CountDownLatch latch = new CountDownLatch(1);
rocketMQTemplate.asyncSend(topic, message, new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
log.info("异步消息发送成功,topic: {}, messageId: {}",
topic, sendResult.getMsgId());
latch.countDown();
}
@Override
public void onException(Throwable e) {
log.error("异步消息发送失败,topic: {}, message: {}", topic, message, e);
latch.countDown();
}
});
// 等待回调执行完成,最多等待5秒
latch.await(5, TimeUnit.SECONDS);
log.info("异步发送等待完成");
}
}
3. 单向发送
单向发送不等待Broker的响应,可靠性最低但性能最好,适用于对可靠性要求不高的日志收集等场景。
java
package com.example.rocketmq.producer;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* 单向消息发送服务
*
* @author example
* @date 2024-01-01
*/
@Slf4j
@Service
public class OnewayMessageProducer {
@Autowired
private RocketMQTemplate rocketMQTemplate;
/**
* 发送单向消息
* 单向发送不等待Broker响应,可靠性最低但性能最好
*
* @param topic 主题名称
* @param message 消息内容
*/
public void sendOnewayMessage(String topic, String message) {
log.info("开始发送单向消息,topic: {}, message: {}", topic, message);
try {
rocketMQTemplate.sendOneWay(topic, message);
log.info("单向消息发送完成(不等待响应),topic: {}", topic);
// 注意:单向发送无法获取发送结果,需要自行处理失败情况
} catch (Exception e) {
log.error("单向消息发送异常,topic: {}, message: {}", topic, message, e);
// 单向发送通常不重试,记录日志即可
}
}
/**
* 发送带Tag的单向消息
*
* @param topic 主题名称
* @param tag 消息标签
* @param message 消息内容
*/
public void sendOnewayWithTag(String topic, String tag, String message) {
log.info("开始发送带Tag的单向消息,topic: {}, tag: {}, message: {}", topic, tag, message);
try {
String destination = topic + ":" + tag;
rocketMQTemplate.sendOneWay(destination, message);
log.info("带Tag的单向消息发送完成,topic: {}, tag: {}", topic, tag);
} catch (Exception e) {
log.error("带Tag的单向消息发送异常,topic: {}, tag: {}", topic, tag, e);
}
}
/**
* 批量发送单向消息
*
* @param topic 主题名称
* @param messages 消息列表
*/
public void sendBatchOneway(String topic, String... messages) {
log.info("开始批量发送单向消息,topic: {}, count: {}", topic, messages.length);
for (String message : messages) {
try {
rocketMQTemplate.sendOneWay(topic, message);
log.debug("单向消息发送:{}", message);
} catch (Exception e) {
log.error("单向消息发送失败:{}", message, e);
}
}
log.info("批量单向消息发送完成,topic: {}, count: {}", topic, messages.length);
}
}
4. 生产者Controller(测试接口)
java
package com.example.rocketmq.controller;
import com.example.rocketmq.producer.AsyncMessageProducer;
import com.example.rocketmq.producer.OnewayMessageProducer;
import com.example.rocketmq.producer.SyncMessageProducer;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.producer.SendResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
/**
* 消息生产者控制器
* 提供REST接口用于测试消息发送
*
* @author example
* @date 2024-01-01
*/
@Slf4j
@RestController
@RequestMapping("/api/producer")
public class ProducerController {
@Autowired
private SyncMessageProducer syncMessageProducer;
@Autowired
private AsyncMessageProducer asyncMessageProducer;
@Autowired
private OnewayMessageProducer onewayMessageProducer;
/**
* 发送同步消息
*
* @param topic 主题名称
* @param message 消息内容
* @return 发送结果
*/
@PostMapping("/sync/send")
public Map<String, Object> sendSyncMessage(
@RequestParam String topic,
@RequestParam String message) {
log.info("接收同步发送请求,topic: {}, message: {}", topic, message);
Map<String, Object> result = new HashMap<>();
try {
SendResult sendResult = syncMessageProducer.sendSimpleMessage(topic, message);
result.put("success", true);
result.put("messageId", sendResult.getMsgId());
result.put("sendStatus", sendResult.getSendStatus());
result.put("queueId", sendResult.getMessageQueue().getQueueId());
result.put("offset", sendResult.getQueueOffset());
} catch (Exception e) {
log.error("同步消息发送失败", e);
result.put("success", false);
result.put("error", e.getMessage());
}
return result;
}
/**
* 发送带Tag的同步消息
*
* @param topic 主题名称
* @param tag 消息标签
* @param message 消息内容
* @return 发送结果
*/
@PostMapping("/sync/sendWithTag")
public Map<String, Object> sendSyncWithTags(
@RequestParam String topic,
@RequestParam String tag,
@RequestParam String message) {
log.info("接收带Tag的同步发送请求,topic: {}, tag: {}, message: {}",
topic, tag, message);
Map<String, Object> result = new HashMap<>();
try {
SendResult sendResult = syncMessageProducer.sendTagMessage(topic, tag, message);
result.put("success", true);
result.put("messageId", sendResult.getMsgId());
result.put("sendStatus", sendResult.getSendStatus());
} catch (Exception e) {
log.error("带Tag的同步消息发送失败", e);
result.put("success", false);
result.put("error", e.getMessage());
}
return result;
}
/**
* 发送异步消息
*
* @param topic 主题名称
* @param message 消息内容
* @return 发送请求状态
*/
@PostMapping("/async/send")
public Map<String, Object> sendAsyncMessage(
@RequestParam String topic,
@RequestParam String message) {
log.info("接收异步发送请求,topic: {}, message: {}", topic, message);
Map<String, Object> result = new HashMap<>();
try {
asyncMessageProducer.sendAsyncMessage(topic, message);
result.put("success", true);
result.put("message", "异步消息已发送,请查看日志获取发送结果");
} catch (Exception e) {
log.error("异步消息发送失败", e);
result.put("success", false);
result.put("error", e.getMessage());
}
return result;
}
/**
* 发送单向消息
*
* @param topic 主题名称
* @param message 消息内容
* @return 发送请求状态
*/
@PostMapping("/oneway/send")
public Map<String, Object> sendOnewayMessage(
@RequestParam String topic,
@RequestParam String message) {
log.info("接收单向发送请求,topic: {}, message: {}", topic, message);
Map<String, Object> result = new HashMap<>();
try {
onewayMessageProducer.sendOnewayMessage(topic, message);
result.put("success", true);
result.put("message", "单向消息已发送,不等待响应");
} catch (Exception e) {
log.error("单向消息发送失败", e);
result.put("success", false);
result.put("error", e.getMessage());
}
return result;
}
}
消费者实现
1. 推模式消费者(Push Consumer)
推模式是RocketMQ的默认消费模式,RocketMQ会主动将消息推送给消费者。
java
package com.example.rocketmq.consumer;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Service;
/**
* 推模式消费者(Push Consumer)
*
* RocketMQ会主动将消息推送给消费者
* 适用于实时性要求高的场景
*
* @author example
* @date 2024-01-01
*/
@Slf4j
@Service
@RocketMQMessageListener(
topic = "test-topic", // 订阅的主题
consumerGroup = "test-consumer-group", // 消费者组名
selectorExpression = "*", // Tag过滤表达式,*表示订阅所有Tag
consumeMode = org.apache.rocketmq.spring.annotation.ConsumeMode.CONCURRENTLY, // 并发消费
messageModel = org.apache.rocketmq.spring.annotation.MessageModel.CLUSTERING // 集群消费模式
)
public class PushStringConsumer implements RocketMQListener<String> {
/**
* 消息消费方法
*
* @param message 消息内容
*/
@Override
public void onMessage(String message) {
log.info("接收到消息: {}", message);
try {
// TODO: 处理业务逻辑
// 模拟业务处理
processMessage(message);
log.info("消息处理成功: {}", message);
} catch (Exception e) {
log.error("消息处理失败: {}", message, e);
// 抛出异常会触发消息重试
throw new RuntimeException("消息处理失败", e);
}
}
/**
* 处理消息的业务逻辑
*
* @param message 消息内容
*/
private void processMessage(String message) {
// 这里编写具体的业务处理逻辑
// 例如:数据库操作、调用其他服务等
log.debug("处理消息业务逻辑: {}", message);
}
}
2. 对象消息消费者
java
package com.example.rocketmq.consumer;
import com.example.rocketmq.entity.Order;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Service;
/**
* 对象消息消费者
*
* 接收并处理对象类型的消息
* 消息会自动反序列化为指定的Java对象
*
* @author example
* @date 2024-01-01
*/
@Slf4j
@Service
@RocketMQMessageListener(
topic = "order-topic",
consumerGroup = "order-consumer-group",
selectorExpression = "*"
)
public class OrderConsumer implements RocketMQListener<Order> {
/**
* 接收Order对象消息
*
* @param order 订单对象
*/
@Override
public void onMessage(Order order) {
log.info("接收到订单消息: orderId={}, userId={}, amount={}",
order.getOrderId(), order.getUserId(), order.getAmount());
try {
// TODO: 处理订单相关的业务逻辑
processOrder(order);
log.info("订单处理成功: orderId={}", order.getOrderId());
} catch (Exception e) {
log.error("订单处理失败: orderId={}", order.getOrderId(), e);
throw new RuntimeException("订单处理失败", e);
}
}
/**
* 处理订单业务逻辑
*
* @param order 订单对象
*/
private void processOrder(Order order) {
// 例如:扣减库存、更新订单状态等
log.info("执行订单处理逻辑: orderId={}", order.getOrderId());
}
}
3. 带Tag过滤的消费者
java
package com.example.rocketmq.consumer;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Service;
/**
* 带Tag过滤的消费者
*
* 使用selectorExpression属性进行消息过滤
* 支持简单的Tag表达式
*
* @author example
* @date 2024-01-01
*/
@Slf4j
@Service
@RocketMQMessageListener(
topic = "order-topic",
consumerGroup = "order-tag-consumer-group",
// 只消费tagA或tagB的消息
selectorExpression = "tagA || tagB"
)
public class TagFilterConsumer implements RocketMQListener<String> {
@Override
public void onMessage(String message) {
log.info("接收到tagA或tagB的消息: {}", message);
try {
// 处理消息
processMessage(message);
} catch (Exception e) {
log.error("消息处理失败: {}", message, e);
throw new RuntimeException("消息处理失败", e);
}
}
private void processMessage(String message) {
log.debug("处理消息: {}", message);
}
}
4. 顺序消息消费者
java
package com.example.rocketmq.consumer;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.ConsumeMode;
import org.apache.rocketmq.spring.annotation.MessageModel;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Service;
/**
* 顺序消息消费者
*
* 通过设置consumeMode为ORDERLY来实现顺序消费
* 保证同一队列中的消息按顺序消费
*
* @author example
* @date 2024-01-01
*/
@Slf4j
@Service
@RocketMQMessageListener(
topic = "order-topic",
consumerGroup = "order-sequential-consumer-group",
selectorExpression = "*",
// 关键:设置为顺序消费模式
consumeMode = ConsumeMode.ORDERLY,
messageModel = MessageModel.CLUSTERING
)
public class SequentialConsumer implements RocketMQListener<String> {
@Override
public void onMessage(String message) {
log.info("接收到顺序消息: {}", message);
try {
// 顺序处理消息
// 注意:因为是顺序消费,处理时间不宜过长,否则会影响后续消息的处理
processSequentialMessage(message);
log.info("顺序消息处理成功: {}", message);
} catch (Exception e) {
log.error("顺序消息处理失败: {}", message, e);
// 顺序消费失败时,该消息会被挂起,不会自动重试
// 需要人工介入或等待Broker重新投递
throw new RuntimeException("顺序消息处理失败", e);
}
}
/**
* 顺序处理消息
*
* @param message 消息内容
*/
private void processSequentialMessage(String message) {
// 按顺序处理业务逻辑
// 例如:同一订单的多个操作必须按顺序执行
log.debug("顺序处理消息: {}", message);
}
}
5. 广播模式消费者
java
package com.example.rocketmq.consumer;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.MessageModel;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Service;
/**
* 广播模式消费者
*
* 使用MessageModel.BROADCASTING模式
* 每个消费者都会收到所有消息
* 适用于需要所有消费者都处理相同消息的场景
*
* @author example
* @date 2024-01-01
*/
@Slf4j
@Service
@RocketMQMessageListener(
topic = "broadcast-topic",
consumerGroup = "broadcast-consumer-group",
selectorExpression = "*",
// 关键:设置为广播模式
messageModel = MessageModel.BROADCASTING
)
public class BroadcastConsumer implements RocketMQListener<String> {
@Override
public void onMessage(String message) {
log.info("接收到广播消息: {}", message);
try {
// 处理广播消息
processBroadcastMessage(message);
} catch (Exception e) {
log.error("广播消息处理失败: {}", message, e);
throw new RuntimeException("广播消息处理失败", e);
}
}
private void processBroadcastMessage(String message) {
// 广播消息处理逻辑
log.debug("处理广播消息: {}", message);
}
}
6. 拉模式消费者(Pull Consumer)
拉模式消费者需要主动从Broker拉取消息,适用于需要精确控制消费进度的场景。
java
package com.example.rocketmq.consumer;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.consumer.DefaultMQPullConsumer;
import org.apache.rocketmq.client.consumer.MQPullConsumerScheduleService;
import org.apache.rocketmq.client.consumer.PullResult;
import org.apache.rocketmq.client.consumer.PullTaskCallback;
import org.apache.rocketmq.client.consumer.PullTaskContext;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.message.MessageQueue;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* 拉模式消费者(Pull Consumer)
*
* 主动从Broker拉取消息,精确控制消费进度
* 适用于需要自定义消费逻辑的场景
*
* @author example
* @date 2024-01-01
*/
@Slf4j
@Component
public class PullConsumer {
@Value("${rocketmq.name-server}")
private String nameServer;
@Value("${rocketmq.consumer.group}")
private String consumerGroup;
private MQPullConsumerScheduleService scheduleService;
/**
* 初始化拉模式消费者
*/
@PostConstruct
public void init() throws Exception {
log.info("初始化拉模式消费者");
scheduleService = new MQPullConsumerScheduleService(consumerGroup);
scheduleService.getDefaultMQPullConsumer().setNamesrvAddr(nameServer);
scheduleService.setMessageModel(org.apache.rocketmq.common.protocol.heartbeat.MessageModel.CLUSTERING);
// 注册拉取任务
scheduleService.registerPullTaskCallback("pull-topic", new PullTaskCallback() {
@Override
public void doPullTask(MessageQueue mq, PullTaskContext context) {
DefaultMQPullConsumer pullConsumer = context.getPullConsumer();
try {
// 获取该队列的拉取offset
long offset = pullConsumer.fetchConsumeOffset(mq, false);
if (offset < 0) {
offset = 0;
}
// 拉取消息(每次最多32条)
PullResult pullResult = pullConsumer.pull(mq, "*", offset, 32);
log.info("拉取消息结果: mq={}, offset={}, pullStatus={}",
mq, offset, pullResult.getPullStatus());
switch (pullResult.getPullStatus()) {
case FOUND:
// 处理拉取到的消息
List<MessageExt> messages = pullResult.getMsgFoundList();
if (messages != null && !messages.isEmpty()) {
for (MessageExt message : messages) {
try {
// 处理消息
processMessage(message);
// 更新消费进度
pullConsumer.updateConsumeOffset(mq, pullResult.getNextBeginOffset());
} catch (Exception e) {
log.error("消息处理失败: messageId={}", message.getMsgId(), e);
// 根据业务需求决定是否更新offset
}
}
}
break;
case NO_MATCHED_MSG:
log.debug("没有匹配的消息: mq={}, offset={}", mq, offset);
break;
case NO_NEW_MSG:
log.debug("没有新消息: mq={}, offset={}", mq, offset);
break;
case OFFSET_ILLEGAL:
log.warn("offset非法: mq={}, offset={}", mq, offset);
// 重置offset
pullConsumer.updateConsumeOffset(mq, pullResult.getNextBeginOffset());
break;
default:
log.warn("未知的拉取状态: {}", pullResult.getPullStatus());
break;
}
} catch (Exception e) {
log.error("拉取消息异常: mq={}", mq, e);
}
}
});
// 启动消费者
scheduleService.start();
log.info("拉模式消费者启动成功");
}
/**
* 处理消息
*
* @param message 消息对象
*/
private void processMessage(MessageExt message) {
String msgBody = new String(message.getBody());
log.info("处理消息: messageId={}, body={}", message.getMsgId(), msgBody);
// TODO: 处理业务逻辑
}
/**
* 销毁消费者
*/
@PreDestroy
public void destroy() {
log.info("关闭拉模式消费者");
if (scheduleService != null) {
scheduleService.shutdown();
}
}
}
消息类型应用
1. 普通消息
普通消息是最基础的消息类型,已在生产者实现部分详细介绍,这里补充完整示例。
java
package com.example.rocketmq.producer;
import com.example.rocketmq.entity.Order;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
/**
* 普通消息生产者
*
* @author example
* @date 2024-01-01
*/
@Slf4j
@Service
public class NormalMessageProducer {
@Autowired
private RocketMQTemplate rocketMQTemplate;
/**
* 发送普通字符串消息
*/
public void sendStringMessage(String topic, String message) {
rocketMQTemplate.convertAndSend(topic, message);
log.info("发送普通字符串消息: topic={}, message={}", topic, message);
}
/**
* 发送对象消息(会自动序列化为JSON)
*/
public void sendObjectMessage(String topic, Order order) {
rocketMQTemplate.convertAndSend(topic, order);
log.info("发送对象消息: topic={}, orderId={}", topic, order.getOrderId());
}
/**
* 发送带属性的Message对象
*/
public void sendMessageWithProperties(String topic, String message) {
Map<String, Object> headers = new HashMap<>();
headers.put("messageType", "NORMAL");
headers.put("timestamp", System.currentTimeMillis());
headers.put("traceId", "trace-" + System.currentTimeMillis());
Message<String> msg = MessageBuilder.withPayload(message)
.copyHeaders(headers)
.build();
rocketMQTemplate.send(topic, msg);
log.info("发送带属性的Message对象: topic={}, message={}", topic, message);
}
/**
* 发送Map类型消息
*/
public void sendMapMessage(String topic, Map<String, Object> data) {
rocketMQTemplate.convertAndSend(topic, data);
log.info("发送Map类型消息: topic={}, keys={}", topic, data.keySet());
}
}
2. 顺序消息
顺序消息保证消息的消费顺序与发送顺序一致,适用于需要严格顺序的场景。
java
package com.example.rocketmq.producer;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
/**
* 顺序消息生产者
*
* 通过设置HashKey将同一业务的消息发送到同一个队列
* 从而保证顺序消费
*
* @author example
* @date 2024-01-01
*/
@Slf4j
@Service
public class OrderedMessageProducer {
@Autowired
private RocketMQTemplate rocketMQTemplate;
/**
* 发送顺序消息(同步方式)
*
* @param topic 主题名称
* @param hashKey 用于选择队列的键,同一hashKey的消息会发送到同一队列
* @param message 消息内容
* @return 发送结果
*/
public SendResult sendOrderedMessage(String topic, String hashKey, String message) {
log.info("发送顺序消息: topic={}, hashKey={}, message={}", topic, hashKey, message);
Message<String> msg = MessageBuilder.withPayload(message).build();
// 使用syncSendOrdered方法发送顺序消息
SendResult sendResult = rocketMQTemplate.syncSendOrdered(topic, msg, hashKey);
log.info("顺序消息发送成功: topic={}, messageId={}, queueId={}",
topic, sendResult.getMsgId(), sendResult.getMessageQueue().getQueueId());
return sendResult;
}
/**
* 发送顺序消息(异步方式)
*
* @param topic 主题名称
* @param hashKey 用于选择队列的键
* @param message 消息内容
*/
public void sendOrderedMessageAsync(String topic, String hashKey, String message) {
log.info("异步发送顺序消息: topic={}, hashKey={}, message={}", topic, hashKey, message);
Message<String> msg = MessageBuilder.withPayload(message).build();
rocketMQTemplate.asyncSendOrdered(topic, msg, new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
log.info("异步顺序消息发送成功: topic={}, messageId={}",
topic, sendResult.getMsgId());
}
@Override
public void onException(Throwable e) {
log.error("异步顺序消息发送失败: topic={}, hashKey={}", topic, hashKey, e);
}
}, hashKey);
}
/**
* 批量发送同一业务ID的顺序消息
*
* @param topic 主题名称
* @param businessId 业务ID(作为hashKey)
* @param messages 消息列表
*/
public void sendOrderedMessagesBatch(String topic, String businessId, List<String> messages) {
log.info("批量发送顺序消息: topic={}, businessId={}, count={}",
topic, businessId, messages.size());
for (int i = 0; i < messages.size(); i++) {
String message = messages.get(i);
try {
// 同步发送,保证顺序
sendOrderedMessage(topic, businessId, message);
// 适当延迟,避免发送过快
if (i < messages.size() - 1) {
Thread.sleep(10);
}
} catch (InterruptedException e) {
log.error("批量发送顺序消息被中断", e);
Thread.currentThread().interrupt();
break;
}
}
log.info("批量顺序消息发送完成: topic={}, businessId={}", topic, businessId);
}
/**
* 订单场景示例:发送订单状态变更消息
* 同一订单的状态变更消息必须按顺序处理
*/
public void sendOrderStatusMessage(String orderId, String oldStatus, String newStatus) {
String topic = "order-status-topic";
// 使用orderId作为hashKey,保证同一订单的消息有序
String hashKey = orderId;
String message = String.format("订单ID: %s, 状态变更: %s -> %s",
orderId, oldStatus, newStatus);
sendOrderedMessage(topic, hashKey, message);
}
/**
* 订单场景示例:发送完整的订单生命周期消息
*/
public void sendOrderLifecycleMessages(String orderId) {
String topic = "order-lifecycle-topic";
// 定义订单生命周期的各个阶段
List<String> lifecycleMessages = new ArrayList<>();
lifecycleMessages.add(orderId + ": 创建订单");
lifecycleMessages.add(orderId + ": 支付订单");
lifecycleMessages.add(orderId + ": 发货");
lifecycleMessages.add(orderId + ": 收货");
lifecycleMessages.add(orderId + ": 评价");
// 批量发送,使用orderId作为hashKey保证顺序
sendOrderedMessagesBatch(topic, orderId, lifecycleMessages);
}
}
3. 事务消息
事务消息是RocketMQ的核心特性之一,用于保证分布式场景下消息发送与本地事务的最终一致性。
java
package com.example.rocketmq.transaction;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionState;
import org.apache.rocketmq.spring.support.RocketMQHeaders;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Component;
import java.util.concurrent.ConcurrentHashMap;
/**
* 事务消息监听器
*
* 负责执行本地事务和回查事务状态
*
* @author example
* @date 2024-01-01
*/
@Slf4j
@Component
public class OrderTransactionListener implements RocketMQLocalTransactionListener {
/**
* 存储事务状态
* key: transactionId
* value: 事务状态 (UNKNOWN=0, COMMIT=1, ROLLBACK=2)
* 生产环境应该使用数据库或Redis持久化存储
*/
private ConcurrentHashMap<String, Integer> localTransactionMap = new ConcurrentHashMap<>();
/**
* 执行本地事务
*
* 当半消息发送成功后,RocketMQ会回调此方法执行本地事务
*
* @param msg 消息对象
* @param arg 业务参数
* @return 事务状态
*/
@Override
public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) {
// 获取事务ID
String transactionId = (String) msg.getHeaders().get(RocketMQHeaders.TRANSACTION_ID);
String orderId = (String) msg.getHeaders().get("orderId");
log.info("执行本地事务: transactionId={}, orderId={}", transactionId, orderId);
try {
// TODO: 执行本地事务(例如:创建订单)
boolean success = executeOrderTransaction(orderId);
if (success) {
// 本地事务成功,提交消息
localTransactionMap.put(transactionId, 1);
log.info("本地事务执行成功,提交消息: transactionId={}", transactionId);
return RocketMQLocalTransactionState.COMMIT;
} else {
// 本地事务失败,回滚消息
localTransactionMap.put(transactionId, 2);
log.warn("本地事务执行失败,回滚消息: transactionId={}", transactionId);
return RocketMQLocalTransactionState.ROLLBACK;
}
} catch (Exception e) {
log.error("本地事务执行异常,返回UNKNOWN状态: transactionId={}", transactionId, e);
// 事务状态未知,等待回查
localTransactionMap.put(transactionId, 0);
return RocketMQLocalTransactionState.UNKNOWN;
}
}
/**
* 检查本地事务状态
*
* 当executeLocalTransaction返回UNKNOWN或超时未响应时,
* RocketMQ会回调此方法检查本地事务状态
*
* @param msg 消息对象
* @return 事务状态
*/
@Override
public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
String transactionId = (String) msg.getHeaders().get(RocketMQHeaders.TRANSACTION_ID);
String orderId = (String) msg.getHeaders().get("orderId");
log.info("检查本地事务状态: transactionId={}, orderId={}", transactionId, orderId);
Integer status = localTransactionMap.get(transactionId);
if (status == null) {
// 未知状态,继续返回UNKNOWN,等待下次回查
log.warn("事务状态未知,返回UNKNOWN: transactionId={}", transactionId);
return RocketMQLocalTransactionState.UNKNOWN;
}
switch (status) {
case 1:
// 已提交
log.info("事务已提交: transactionId={}", transactionId);
return RocketMQLocalTransactionState.COMMIT;
case 2:
// 已回滚
log.info("事务已回滚: transactionId={}", transactionId);
return RocketMQLocalTransactionState.ROLLBACK;
case 0:
default:
// 未知或进行中,继续返回UNKNOWN
log.info("事务进行中或状态未知,返回UNKNOWN: transactionId={}", transactionId);
return RocketMQLocalTransactionState.UNKNOWN;
}
}
/**
* 执行订单事务(模拟)
*
* @param orderId 订单ID
* @return 执行结果
*/
private boolean executeOrderTransaction(String orderId) {
try {
// TODO: 这里执行实际的业务逻辑
// 例如:
// 1. 开启数据库事务
// 2. 插入订单记录
// 3. 扣减库存
// 4. 提交事务
log.info("执行订单事务逻辑: orderId={}", orderId);
// 模拟事务执行
// 实际业务中应该根据数据库操作结果返回
return true;
} catch (Exception e) {
log.error("执行订单事务异常: orderId={}", orderId, e);
return false;
}
}
}
java
package com.example.rocketmq.producer;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.producer.TransactionSendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
/**
* 事务消息生产者
*
* 用于发送事务消息,保证消息发送与本地事务的最终一致性
*
* @author example
* @date 2024-01-01
*/
@Slf4j
@Service
public class TransactionMessageProducer {
@Autowired
private RocketMQTemplate rocketMQTemplate;
/**
* 发送事务消息
*
* @param topic 主题名称
* @param message 消息内容
* @param orderId 业务ID(用于事务回查)
* @return 事务发送结果
*/
public TransactionSendResult sendTransactionMessage(String topic, String message, String orderId) {
log.info("发送事务消息: topic={}, message={}, orderId={}", topic, message, orderId);
// 构建消息,添加业务属性
Message<String> msg = MessageBuilder
.withPayload(message)
.setHeader("orderId", orderId)
.setHeader("timestamp", System.currentTimeMillis())
.build();
// 事务名称,必须与@RocketMQTransactionListener的transName属性一致
String transactionName = "order-transaction";
try {
// 发送事务消息
TransactionSendResult result = rocketMQTemplate.sendMessageInTransaction(
transactionName, // 事务名称
topic, // 主题
msg, // 消息
null // 业务参数
);
log.info("事务消息发送完成: topic={}, messageId={}, transactionId={}, localTransactionState={}",
topic, result.getMsgId(), result.getTransactionId(), result.getLocalTransactionState());
return result;
} catch (Exception e) {
log.error("事务消息发送失败: topic={}, orderId={}", topic, orderId, e);
throw new RuntimeException("事务消息发送失败", e);
}
}
/**
* 发送订单创建事务消息
*
* @param orderId 订单ID
* @param userId 用户ID
* @param amount 金额
*/
public void sendOrderCreatedMessage(String orderId, String userId, String amount) {
String topic = "order-transaction-topic";
String message = String.format("订单创建: orderId=%s, userId=%s, amount=%s",
orderId, userId, amount);
sendTransactionMessage(topic, message, orderId);
}
}
4. 延时消息
延时消息允许消息在指定时间后才可被消费,适用于订单超时取消等场景。
java
package com.example.rocketmq.producer;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
/**
* 延时消息生产者
*
* RocketMQ支持特定的延时级别:
* 1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
*
* @author example
* @date 2024-01-01
*/
@Slf4j
@Service
public class DelayMessageProducer {
@Autowired
private RocketMQTemplate rocketMQTemplate;
/**
* 发送延时消息
*
* @param topic 主题名称
* @param message 消息内容
* @param delayLevel 延时级别(1-18)
*/
public void sendDelayMessage(String topic, String message, int delayLevel) {
log.info("发送延时消息: topic={}, message={}, delayLevel={}", topic, message, delayLevel);
Message<String> msg = MessageBuilder.withPayload(message).build();
// 发送延时消息
rocketMQTemplate.syncSend(topic, msg, 3000, delayLevel);
log.info("延时消息发送成功: topic={}, delayLevel={}", topic, delayLevel);
}
/**
* 发送订单超时取消消息
* 延时级别16 = 30分钟
*
* @param orderId 订单ID
*/
public void sendOrderTimeoutMessage(String orderId) {
String topic = "order-timeout-topic";
String message = "订单超时未支付,需要取消: orderId=" + orderId;
// 延时级别16表示30分钟后可消费
sendDelayMessage(topic, message, 16);
log.info("订单超时检查消息已发送: orderId={}, 延时时间=30分钟", orderId);
}
/**
* 延时级别对照表
*
* level 1: 1秒
* level 2: 5秒
* level 3: 10秒
* level 4: 30秒
* level 5: 1分钟
* level 6: 2分钟
* level 7: 3分钟
* level 8: 4分钟
* level 9: 5分钟
* level 10: 6分钟
* level 11: 7分钟
* level 12: 8分钟
* level 13: 9分钟
* level 14: 10分钟
* level 15: 20分钟
* level 16: 30分钟
* level 17: 1小时
* level 18: 2小时
*/
public int getDelayLevel(String delayDescription) {
switch (delayDescription) {
case "1s": return 1;
case "5s": return 2;
case "10s": return 3;
case "30s": return 4;
case "1m": return 5;
case "2m": return 6;
case "3m": return 7;
case "4m": return 8;
case "5m": return 9;
case "6m": return 10;
case "7m": return 11;
case "8m": return 12;
case "9m": return 13;
case "10m": return 14;
case "20m": return 15;
case "30m": return 16;
case "1h": return 17;
case "2h": return 18;
default: return 1;
}
}
}
完整代码示例
1. 实体类
java
package com.example.rocketmq.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
/**
* 订单实体类
*
* @author example
* @date 2024-01-01
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Order implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 订单ID
*/
private String orderId;
/**
* 用户ID
*/
private String userId;
/**
* 订单金额
*/
private BigDecimal amount;
/**
* 订单状态
*/
private String status;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新时间
*/
private Date updateTime;
}
2. 主启动类
java
package com.example.rocketmq;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* RocketMQ Spring Boot 整合示例启动类
*
* @author example
* @date 2024-01-01
*/
@SpringBootApplication
public class RocketmqSpringbootDemoApplication {
public static void main(String[] args) {
SpringApplication.run(RocketmqSpringbootDemoApplication.class, args);
System.out.println("RocketMQ Spring Boot整合示例启动成功!");
}
}
3. 单元测试
java
package com.example.rocketmq;
import com.example.rocketmq.producer.AsyncMessageProducer;
import com.example.rocketmq.producer.OnewayMessageProducer;
import com.example.rocketmq.producer.SyncMessageProducer;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* 生产者单元测试
*
* @author example
* @date 2024-01-01
*/
@SpringBootTest
public class ProducerTest {
@Autowired
private SyncMessageProducer syncMessageProducer;
@Autowired
private AsyncMessageProducer asyncMessageProducer;
@Autowired
private OnewayMessageProducer onewayMessageProducer;
/**
* 测试同步消息发送
*/
@Test
public void testSyncSend() {
System.out.println("=== 测试同步消息发送 ===");
syncMessageProducer.sendSimpleMessage("test-topic", "这是一条同步消息");
syncMessageProducer.sendTagMessage("test-topic", "tagA", "这是一条带tagA的同步消息");
syncMessageProducer.sendTagMessage("test-topic", "tagB", "这是一条带tagB的同步消息");
System.out.println("同步消息发送测试完成");
}
/**
* 测试异步消息发送
*/
@Test
public void testAsyncSend() throws InterruptedException {
System.out.println("=== 测试异步消息发送 ===");
asyncMessageProducer.sendAsyncMessage("test-topic", "这是一条异步消息");
// 等待异步回调执行
Thread.sleep(2000);
System.out.println("异步消息发送测试完成");
}
/**
* 测试单向消息发送
*/
@Test
public void testOnewaySend() {
System.out.println("=== 测试单向消息发送 ===");
onewayMessageProducer.sendOnewayMessage("test-topic", "这是一条单向消息");
System.out.println("单向消息发送测试完成");
}
/**
* 测试批量发送
*/
@Test
public void testBatchSend() {
System.out.println("=== 测试批量发送 ===");
for (int i = 1; i <= 10; i++) {
String message = "批量消息 #" + i;
syncMessageProducer.sendSimpleMessage("test-topic", message);
}
System.out.println("批量发送测试完成");
}
/**
* 压力测试
*/
@Test
public void testPressure() throws InterruptedException {
System.out.println("=== 压力测试开始 ===");
int messageCount = 1000;
CountDownLatch latch = new CountDownLatch(messageCount);
long startTime = System.currentTimeMillis();
for (int i = 0; i < messageCount; i++) {
final int index = i;
new Thread(() -> {
try {
syncMessageProducer.sendSimpleMessage("test-topic", "压测消息 #" + index);
} finally {
latch.countDown();
}
}).start();
}
latch.await(10, TimeUnit.SECONDS);
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
System.out.println("压力测试完成:");
System.out.println("发送消息数: " + messageCount);
System.out.println("总耗时: " + duration + "ms");
System.out.println("平均TPS: " + (messageCount * 1000.0 / duration));
}
}
4. Postman测试示例
1. 发送同步消息
POST http://localhost:8080/api/producer/sync/send
Content-Type: application/x-www-form-urlencoded
topic=test-topic&message=Hello RocketMQ
响应示例:
json
{
"success": true,
"messageId": "7F0000017B484724B2BC2E1E9B400000",
"sendStatus": "SEND_OK",
"queueId": 0,
"offset": 0
}
2. 发送带Tag的同步消息
POST http://localhost:8080/api/producer/sync/sendWithTag
Content-Type: application/x-www-form-urlencoded
topic=test-topic&tag=tagA&message=带Tag的消息
3. 发送异步消息
POST http://localhost:8080/api/producer/async/send
Content-Type: application/x-www-form-urlencoded
topic=test-topic&message=异步消息
4. 发送单向消息
POST http://localhost:8080/api/producer/oneway/send
Content-Type: application/x-www-form-urlencoded
topic=test-topic&message=单向消息
常见问题解决
1. 连接问题
问题:无法连接到NameServer
connect to <127.0.0.1:9876> failed
解决方案:
- 检查NameServer是否启动
- 检查防火墙设置
- 检查配置文件中的name-server地址是否正确
- 确认端口9876未被占用
bash
# 检查端口占用
netstat -ano | findstr 9876
# 检查NameServer进程
jps | findstr NamesrvStartup
2. 内存问题
问题:启动Broker时内存不足
Error: occurred during initialization of VM
Could not reserve enough space for object heap
解决方案:
修改runbroker.cmd(Windows)或runbroker.sh(Linux)中的JVM参数
bash
# Windows - 编辑 runbroker.cmd
# 将 -Xms2g -Xmx2g 改为 -Xms512m -Xmx512m
# Linux - 编辑 runbroker.sh
# 将 JAVA_OPT="${JAVA_OPT} -Xms2g -Xmx2g"
# 改为 JAVA_OPT="${JAVA_OPT} -Xms512m -Xmx512m"
3. 消息消费失败
问题:消费者一直重试,消息堆积
Consume Message Failed, message will retry
解决方案:
- 检查消费者代码逻辑
- 查看消费者日志定位错误原因
- 考虑实现幂等性处理
- 对于无法处理的消息,记录并手动处理,避免无限重试
java
@Override
public void onMessage(String message) {
try {
// 业务处理逻辑
processMessage(message);
} catch (BusinessException e) {
log.error("业务异常,消息处理失败: {}", message, e);
// 业务异常,不再重试,直接消费掉
// 可以记录到数据库进行人工处理
} catch (Exception e) {
log.error("系统异常,触发重试: {}", message, e);
// 系统异常,抛出异常触发重试
throw new RuntimeException(e);
}
}
4. 顺序消息乱序
问题:顺序消息出现乱序
解决方案:
- 确保生产者使用
syncSendOrdered方法发送 - 确保使用相同的hashKey
- 确保消费者设置
consumeMode = ConsumeMode.ORDERLY - 消费者处理逻辑不能抛出异常,否则消息会被挂起
5. 事务消息状态未知
问题:事务消息一直处于UNKNOWN状态,不断回查
解决方案:
- 检查
executeLocalTransaction方法的返回值 - 检查
checkLocalTransaction方法是否正确实现 - 确保事务状态被正确存储(生产环境使用数据库或Redis)
- 设置合理的事务超时时间
java
// 在配置文件中设置事务超时时间
rocketmq.producer.transaction-timeout=60000 // 60秒
6. 消息重复消费
问题:消费者收到重复消息
解决方案:
实现消息幂等性处理
java
@Override
public void onMessage(String message) {
// 使用Redis实现幂等性
String messageId = getMessageId(message); // 从消息中提取唯一ID
String redisKey = "msg:processed:" + messageId;
// 尝试设置Redis key
boolean isNew = redisTemplate.opsForValue()
.setIfAbsent(redisKey, "1", 24, TimeUnit.HOURS);
if (!isNew) {
log.info("消息已处理过,跳过: messageId={}", messageId);
return;
}
// 处理消息
processMessage(message);
}
7. 消费者启动失败
问题:消费者启动时抛出异常
The consumer group has been created before
解决方案:
消费者组名必须唯一,确保不同的应用使用不同的消费者组名。
8. 消息发送超时
问题:消息发送超时
解决方案:
- 增加发送超时时间配置
- 检查网络连接
- 检查Broker负载情况
- 考虑使用异步发送提高吞吐量
yaml
rocketmq:
producer:
send-message-timeout: 10000 # 增加到10秒
总结与扩展
1. 性能优化建议
生产者优化
- 批量发送:尽量使用批量发送减少网络请求
- 异步发送:高吞吐场景使用异步发送
- 合理设置超时:根据业务需求设置合理的超时时间
- 消息压缩:大消息开启压缩功能
- 连接池优化:调整Netty连接参数
java
// 批量发送示例
List<Message> messages = new ArrayList<>();
for (int i = 0; i < 100; i++) {
messages.add(MessageBuilder.withPayload("消息" + i).build());
}
rocketMQTemplate.syncSend(topic, messages);
消费者优化
- 并发消费:适当增加消费线程数
- 批量消费:开启批量消费减少拉取次数
- 消费限流:设置合理的消费限流参数
- 消息过滤:使用Tag减少不必要的消息传输
- 异步处理:消费端异步处理提高吞吐
yaml
rocketmq:
consumer:
consume-thread-min: 20
consume-thread-max: 50
pull-batch-size: 64
Broker优化
- 内存配置:合理设置JVM堆内存
- 刷盘策略:根据可靠性要求选择刷盘策略
- 副本同步:配置合适的主从复制策略
- 清理策略:定期清理过期消息
- 网络优化:优化网络参数
properties
# Broker配置优化
flushDiskType=ASYNC_FLUSH # 异步刷盘(高性能)
brokerRole=ASYNC_MASTER # 异步主从复制(高性能)
deleteWhen=04 # 每天凌晨4点执行清理
fileReservedTime=48 # 保留48小时
2. 生产环境注意事项
集群部署
- 多NameServer部署:至少2-3个NameServer节点
- Broker集群:至少2个Broker节点,配置主从复制
- 负载均衡:配置负载均衡策略
- 监控告警:部署监控系统,设置告警阈值
- 容灾方案:制定完善的容灾恢复方案
安全配置
- ACL控制:开启ACL权限控制
- 网络安全:配置防火墙规则
- 数据加密:敏感数据加密传输
- 访问审计:记录访问日志
- 密码安全:使用强密码并定期更换
properties
# 开启ACL
aclEnable=true
# 白名单配置
whiteListEnable=true
监控指标
- 消息TPS:监控发送和消费TPS
- 消息堆积:监控消息堆积量
- 响应时间:监控消息处理延迟
- 错误率:监控发送和消费错误率
- 资源使用:监控CPU、内存、磁盘使用率
推荐监控工具:
- RocketMQ Dashboard
- Prometheus + Grafana
- ELK日志分析
运维规范
- 版本管理:统一版本管理,做好兼容性测试
- 配置管理:使用配置中心统一管理配置
- 变更流程:严格执行变更审批流程
- 备份恢复:定期备份重要数据
- 文档维护:维护完整的运维文档
3. RocketMQ高级特性
1. 消息轨迹
开启消息轨迹功能,可以追踪消息的完整生命周期。
java
// 生产者端开启消息轨迹
rocketmq.producer.enable-msg-trace=true
rocketmq.producer.customized-trace-topic=RMQ_SYS_TRACE_TOPIC
// 消费者端开启消息轨迹
rocketmq.consumer.enable-msg-trace=true
rocketmq.consumer.customized-trace-topic=RMQ_SYS_TRACE_TOPIC
2. 消息过滤
RocketMQ支持Tag过滤和SQL92语法过滤。
java
// Tag过滤
@RocketMQMessageListener(
topic = "test-topic",
selectorExpression = "tagA || tagB" // 订阅tagA或tagB
)
// SQL92过滤
@RocketMQMessageListener(
topic = "test-topic",
selectorType = SelectorType.SQL92,
selectorExpression = "amount > 1000 AND status = 'PAID'"
)
3. 死信队列
消费失败的消息会进入死信队列,需要专门处理。
java
// 死信队列Topic命名规则:%DLQ%消费者组名
// 示例:%DLQ%test-consumer-group
// 处理死信队列消息
@Service
@RocketMQMessageListener(
topic = "%DLQ%test-consumer-group",
consumerGroup = "dlq-handler-group"
)
public class DeadLetterQueueHandler implements RocketMQListener<MessageExt> {
@Override
public void onMessage(MessageExt message) {
log.error("收到死信消息: msgId={}", message.getMsgId());
// 记录到数据库
saveToDeadLetterTable(message);
// 人工介入处理
// 发送告警通知
sendAlert(message);
}
}
4. 定时消息(5.0+特性)
RocketMQ 5.0支持任意时间的定时消息。
java
// 发送定时消息
Message<String> msg = MessageBuilder
.withPayload(message)
.setHeader("__STARTDELIVERTIME", System.currentTimeMillis() + 60000)
.build();
rocketMQTemplate.send(topic, msg);
4. 最佳实践总结
消息设计
- 消息大小:单条消息不超过4MB
- 消息结构:使用JSON格式,便于解析
- 唯一标识:每条消息设置唯一key
- 业务字段:包含必要的业务字段
- 版本管理:消息结构升级做好版本兼容
错误处理
- 重试策略:合理设置重试次数和间隔
- 降级方案:准备降级方案应对故障
- 告警机制:关键异常及时告警
- 日志记录:记录详细的错误日志
- 故障恢复:制定完善的故障恢复流程
测试策略
- 单元测试:覆盖核心业务逻辑
- 集成测试:测试与RocketMQ的集成
- 压力测试:模拟高并发场景
- 故障测试:模拟各种故障场景
- 回归测试:版本升级后充分测试
5. 参考资源
- 官方文档:https://rocketmq.apache.org/zh/docs/
- GitHub仓库:https://github.com/apache/rocketmq
- Spring Boot Starter:https://github.com/apache/rocketmq-spring
- RocketMQ Dashboard:https://github.com/apache/rocketmq-dashboard
结语
本文详细介绍了RocketMQ的核心概念、环境搭建、与Spring Boot的完整整合过程,包括普通消息、顺序消息、事务消息、延时消息等多种消息类型的实现。通过本文的学习,开发者可以快速掌握RocketMQ的使用方法,并在实际项目中应用。
RocketMQ作为一款成熟的消息中间件,在金融级场景下的可靠性表现尤为突出。在实际项目中,需要根据业务需求选择合适的消息类型和配置参数,同时做好监控、告警和容灾准备。