RocketMQ 实战指南:分布式消息中间件深度实践
目录
- 1. RocketMQ 简介
- 2. 核心架构
- 3. 环境搭建
- 4. 生产者实战
- 5. 消费者实战
- 6. 消息类型详解
- 7. 顺序消息
- 8. 事务消息
- 9. 消息过滤
- 10. 死信队列与消息重试
- 11. Spring Boot 集成
- 12. 生产环境最佳实践
- 13. 监控与运维
- 14. 总结
1. RocketMQ 简介
Apache RocketMQ 是阿里巴巴开源的分布式消息中间件,具有低延迟、高可靠、万亿级消息容量的特点,广泛应用于电商、金融等业务场景。
1.1 核心特性
- 高性能: 单机支持万级TPS,集群支持百万级TPS
- 高可用: 主从架构,支持故障自动切换
- 海量消息堆积: 支持亿级消息堆积,不影响性能
- 顺序消息: 支持全局和分区顺序消息
- 事务消息: 支持分布式事务消息
- 定时/延时消息: 支持18个级别的延时消息
- 消息回溯: 支持按时间回溯消费
- 死信队列: 自动处理消费失败的消息
1.2 应用场景
RocketMQ 典型应用场景
1. 异步解耦
订单系统 ──▶ RocketMQ ──▶ 库存系统
├──▶ 积分系统
└──▶ 通知系统
2. 流量削峰
高并发请求 ──▶ RocketMQ ──▶ 平滑处理
(10万/秒) (1万/秒)
3. 分布式事务
下单 ──▶ 扣库存 ──▶ 扣余额 ──▶ 发货
(本地) (MQ事务)
4. 数据同步
MySQL ──▶ Binlog ──▶ RocketMQ ──▶ ES/Redis
5. 日志收集
应用日志 ──▶ RocketMQ ──▶ 日志分析系统
1.3 RocketMQ vs Kafka
性能与特性对比
┌─────────────────┬──────────────┬──────────────┐
│ Feature │ RocketMQ │ Kafka │
├─────────────────┼──────────────┼──────────────┤
│ 吞吐量 │ 10万级 │ 100万级 │
├─────────────────┼──────────────┼──────────────┤
│ 延迟 │ 毫秒级 │ 毫秒级 │
├─────────────────┼──────────────┼──────────────┤
│ 消息堆积 │ 支持 │ 支持 │
├─────────────────┼──────────────┼──────────────┤
│ 顺序消息 │ 支持 │ 分区有序 │
├─────────────────┼──────────────┼──────────────┤
│ 事务消息 │ 支持 │ 不支持 │
├─────────────────┼──────────────┼──────────────┤
│ 延时消息 │ 支持 │ 不支持 │
├─────────────────┼──────────────┼──────────────┤
│ 消息回溯 │ 支持 │ 支持 │
└─────────────────┴──────────────┴──────────────┘
2. 核心架构
2.1 整体架构
RocketMQ 核心架构
┌────────────────────────────────────────────────┐
│ NameServer Cluster │
│ (路由注册中心,类似服务注册中心) │
│ - Broker 注册 │
│ - 路由信息管理 │
│ - 无状态,可随时扩容 │
└────────┬───────────────────────┬────────────────┘
│ │
心跳/注册 路由发现
│ │
┌────▼────┐ ┌─────▼──────┐
│ Broker │ │ Producer │
│ Cluster │ │ (生产者) │
└────┬────┘ └─────┬──────┘
│ │
│ 发送消息
│ │
│ ┌────▼────┐
│ │ Topic │
│ │ Queue │
│ └────┬────┘
│ │
│ 拉取消息
│ │
│ ┌────▼──────┐
└──────────────────│ Consumer │
│ (消费者) │
└───────────┘
Broker 主从架构:
┌──────────────┐ ┌──────────────┐
│ Master │────────▶│ Slave │
│ (读写) │ 同步 │ (只读) │
└──────────────┘ └──────────────┘
2.2 消息存储模型
消息存储结构
Topic: OrderTopic
├── Queue 0 (MessageQueue)
│ └── ConsumeQueue (消费队列索引)
├── Queue 1
│ └── ConsumeQueue
├── Queue 2
│ └── ConsumeQueue
└── Queue 3
└── ConsumeQueue
│
▼
CommitLog (统一消息日志)
┌────────────────────────────┐
│ Message 1 (Topic A, Q0) │
├────────────────────────────┤
│ Message 2 (Topic B, Q1) │
├────────────────────────────┤
│ Message 3 (Topic A, Q2) │
├────────────────────────────┤
│ Message 4 (Topic C, Q0) │
└────────────────────────────┘
特点:
1. CommitLog: 顺序写,所有消息存储在一起
2. ConsumeQueue: 消费索引,指向 CommitLog
3. IndexFile: 消息索引,支持按 Key 查询
2.3 消息消费模式
消费模式
1. 集群消费 (Clustering)
Consumer Group: group-1
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Queue 0 │─▶│Consumer A│ │ │
├──────────┤ ├──────────┤ │ │
│ Queue 1 │─▶│Consumer B│ │ │
├──────────┤ ├──────────┤ │ │
│ Queue 2 │─▶│Consumer C│ │ │
├──────────┤ └──────────┘ │ │
│ Queue 3 │────────────────│Consumer D│
└──────────┘ └──────────┘
每条消息只被消费一次,负载均衡
2. 广播消费 (Broadcasting)
┌──────────┐
│ Message │
└────┬─────┘
├────────┬────────┬────────┐
▼ ▼ ▼ ▼
┌────────┐┌────────┐┌────────┐┌────────┐
│Consumer││Consumer││Consumer││Consumer│
│ A ││ B ││ C ││ D │
└────────┘└────────┘└────────┘└────────┘
每条消息被所有消费者消费
3. 环境搭建
3.1 Docker 快速启动
# docker-compose.yml
version: '3'
services:
namesrv:
image: apache/rocketmq:5.1.4
container_name: rmqnamesrv
ports:
- 9876:9876
environment:
JAVA_OPT_EXT: "-Duser.home=/opt -Xms512M -Xmx512M -Xmn128m"
command: sh mqnamesrv
broker:
image: apache/rocketmq:5.1.4
container_name: rmqbroker
ports:
- 10909:10909
- 10911:10911
- 10912:10912
environment:
NAMESRV_ADDR: "namesrv:9876"
JAVA_OPT_EXT: "-Duser.home=/opt -Xms512M -Xmx512M -Xmn128m"
command: sh mqbroker -c /opt/rocketmq/conf/broker.conf
depends_on:
- namesrv
volumes:
- ./broker.conf:/opt/rocketmq/conf/broker.conf
console:
image: styletang/rocketmq-console-ng
container_name: rmqconsole
ports:
- 8080:8080
environment:
JAVA_OPTS: "-Drocketmq.namesrv.addr=namesrv:9876"
depends_on:
- namesrv
# broker.conf
brokerClusterName = DefaultCluster
brokerName = broker-a
brokerId = 0
deleteWhen = 04
fileReservedTime = 48
brokerRole = ASYNC_MASTER
flushDiskType = ASYNC_FLUSH
brokerIP1 = 宿主机IP
# 启动
docker-compose up -d
3.2 Maven 依赖
<!-- pom.xml -->
<properties>
<rocketmq.version>5.1.4</rocketmq.version>
<rocketmq.spring.version>2.2.3</rocketmq.spring.version>
</properties>
<dependencies>
<!-- RocketMQ 客户端 -->
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>${rocketmq.version}</version>
</dependency>
<!-- Spring Boot RocketMQ Starter -->
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>${rocketmq.spring.version}</version>
</dependency>
<!-- JSON 序列化 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.43</version>
</dependency>
<!-- Lombok (可选) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
4. 生产者实战
4.1 基础生产者
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.client.producer.SendStatus;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.common.RemotingHelper;
/**
* RocketMQ 基础生产者
*/
public class BasicProducer {
private static final String NAME_SERVER = "localhost:9876";
private static final String TOPIC = "OrderTopic";
private static final String PRODUCER_GROUP = "order-producer-group";
/**
* 同步发送消息
*/
public static void syncSend() throws Exception {
// 创建生产者
DefaultMQProducer producer = new DefaultMQProducer(PRODUCER_GROUP);
producer.setNamesrvAddr(NAME_SERVER);
// 生产者配置
producer.setSendMsgTimeout(3000); // 发送超时 3秒
producer.setRetryTimesWhenSendFailed(2); // 同步发送失败重试2次
producer.setRetryTimesWhenSendAsyncFailed(2); // 异步发送失败重试2次
// 启动生产者
producer.start();
System.out.println("生产者启动成功");
try {
for (int i = 0; i < 10; i++) {
// 创建消息
Message msg = new Message(
TOPIC, // Topic
"TagA", // Tag
("Order_" + i).getBytes(RemotingHelper.DEFAULT_CHARSET) // Body
);
// 同步发送
SendResult sendResult = producer.send(msg);
System.out.printf(
"消息发送成功 - MsgId: %s, Status: %s, QueueId: %d, Offset: %d\n",
sendResult.getMsgId(),
sendResult.getSendStatus(),
sendResult.getMessageQueue().getQueueId(),
sendResult.getQueueOffset()
);
}
} finally {
// 关闭生产者
producer.shutdown();
}
}
/**
* 异步发送消息
*/
public static void asyncSend() throws Exception {
DefaultMQProducer producer = new DefaultMQProducer(PRODUCER_GROUP);
producer.setNamesrvAddr(NAME_SERVER);
producer.start();
try {
for (int i = 0; i < 10; i++) {
final int index = i;
Message msg = new Message(
TOPIC,
"TagB",
("AsyncOrder_" + i).getBytes(RemotingHelper.DEFAULT_CHARSET)
);
// 异步发送
producer.send(msg, new org.apache.rocketmq.client.producer.SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
System.out.printf(
"消息 %d 异步发送成功 - MsgId: %s\n",
index,
sendResult.getMsgId()
);
}
@Override
public void onException(Throwable e) {
System.err.printf(
"消息 %d 异步发送失败: %s\n",
index,
e.getMessage()
);
}
});
}
// 等待异步发送完成
Thread.sleep(3000);
} finally {
producer.shutdown();
}
}
/**
* 单向发送 (无需等待响应)
*/
public static void onewaySend() throws Exception {
DefaultMQProducer producer = new DefaultMQProducer(PRODUCER_GROUP);
producer.setNamesrvAddr(NAME_SERVER);
producer.start();
try {
for (int i = 0; i < 10; i++) {
Message msg = new Message(
TOPIC,
"TagC",
("OnewayOrder_" + i).getBytes(RemotingHelper.DEFAULT_CHARSET)
);
// 单向发送 (不关心结果,最快)
producer.sendOneway(msg);
System.out.println("消息 " + i + " 单向发送");
}
} finally {
producer.shutdown();
}
}
/**
* 发送到指定队列
*/
public static void sendToQueue() throws Exception {
DefaultMQProducer producer = new DefaultMQProducer(PRODUCER_GROUP);
producer.setNamesrvAddr(NAME_SERVER);
producer.start();
try {
Message msg = new Message(
TOPIC,
"TagD",
"SpecificQueueMessage".getBytes(RemotingHelper.DEFAULT_CHARSET)
);
// 发送到指定队列
SendResult result = producer.send(msg,
new org.apache.rocketmq.client.producer.MessageQueueSelector() {
@Override
public org.apache.rocketmq.common.message.MessageQueue select(
java.util.List<org.apache.rocketmq.common.message.MessageQueue> mqs,
Message msg,
Object arg) {
// 选择队列 0
return mqs.get(0);
}
},
null);
System.out.println("消息发送到队列: " + result.getMessageQueue().getQueueId());
} finally {
producer.shutdown();
}
}
public static void main(String[] args) throws Exception {
System.out.println("=== 同步发送 ===");
syncSend();
System.out.println("\n=== 异步发送 ===");
asyncSend();
System.out.println("\n=== 单向发送 ===");
onewaySend();
}
}
4.2 批量发送
import java.util.ArrayList;
import java.util.List;
/**
* 批量发送消息
*/
public class BatchProducer {
/**
* 批量发送 (单次最大1MB)
*/
public static void batchSend() throws Exception {
DefaultMQProducer producer = new DefaultMQProducer("batch-producer-group");
producer.setNamesrvAddr("localhost:9876");
producer.start();
try {
String topic = "BatchTopic";
List<Message> messages = new ArrayList<>();
// 创建批量消息
for (int i = 0; i < 100; i++) {
Message msg = new Message(
topic,
"TagBatch",
("BatchMessage_" + i).getBytes(RemotingHelper.DEFAULT_CHARSET)
);
messages.add(msg);
}
// 批量发送
SendResult sendResult = producer.send(messages);
System.out.println("批量发送成功: " + sendResult.getMsgId());
} finally {
producer.shutdown();
}
}
/**
* 分批发送 (处理超过1MB的消息列表)
*/
public static void splitBatchSend() throws Exception {
DefaultMQProducer producer = new DefaultMQProducer("batch-producer-group");
producer.setNamesrvAddr("localhost:9876");
producer.start();
try {
String topic = "BatchTopic";
List<Message> messages = new ArrayList<>();
// 创建大量消息
for (int i = 0; i < 1000; i++) {
messages.add(new Message(
topic,
"TagBatch",
("Message_" + i).getBytes(RemotingHelper.DEFAULT_CHARSET)
));
}
// 分批发送
ListSplitter splitter = new ListSplitter(messages);
while (splitter.hasNext()) {
List<Message> batch = splitter.next();
SendResult result = producer.send(batch);
System.out.println("批次发送成功: " + result.getMsgId());
}
} finally {
producer.shutdown();
}
}
/**
* 消息列表分割器 (1MB 限制)
*/
static class ListSplitter implements java.util.Iterator<List<Message>> {
private final int SIZE_LIMIT = 1024 * 1024; // 1MB
private final List<Message> messages;
private int currIndex = 0;
public ListSplitter(List<Message> messages) {
this.messages = messages;
}
@Override
public boolean hasNext() {
return currIndex < messages.size();
}
@Override
public List<Message> next() {
int nextIndex = currIndex;
int totalSize = 0;
for (; nextIndex < messages.size(); nextIndex++) {
Message message = messages.get(nextIndex);
int tmpSize = message.getTopic().length() + message.getBody().length;
// 消息本身超过限制
if (tmpSize > SIZE_LIMIT) {
if (nextIndex - currIndex == 0) {
nextIndex++;
}
break;
}
// 累加超过限制
if (tmpSize + totalSize > SIZE_LIMIT) {
break;
}
totalSize += tmpSize;
}
List<Message> subList = messages.subList(currIndex, nextIndex);
currIndex = nextIndex;
return subList;
}
}
public static void main(String[] args) throws Exception {
batchSend();
splitBatchSend();
}
}
5. 消费者实战
5.1 推模式消费者
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.*;
import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
import org.apache.rocketmq.common.message.MessageExt;
import java.util.List;
/**
* 推模式消费者 (Push Consumer)
*/
public class PushConsumer {
/**
* 并发消费 (无序)
*/
public static void concurrentConsume() throws Exception {
// 创建消费者
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("order-consumer-group");
consumer.setNamesrvAddr("localhost:9876");
// 订阅 Topic
consumer.subscribe("OrderTopic", "TagA || TagB"); // 订阅多个 Tag
// 消费位置
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
// 消费配置
consumer.setConsumeThreadMin(20); // 最小线程数
consumer.setConsumeThreadMax(20); // 最大线程数
consumer.setConsumeMessageBatchMaxSize(1); // 每次消费消息数
// 注册并发消费监听器
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(
List<MessageExt> msgs,
ConsumeConcurrentlyContext context) {
for (MessageExt msg : msgs) {
try {
String body = new String(msg.getBody(), RemotingHelper.DEFAULT_CHARSET);
System.out.printf(
"收到消息 - MsgId: %s, Topic: %s, Tag: %s, QueueId: %d, Body: %s\n",
msg.getMsgId(),
msg.getTopic(),
msg.getTags(),
msg.getQueueId(),
body
);
// 业务处理
processMessage(body);
// 返回成功
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
} catch (Exception e) {
System.err.println("消息处理失败: " + e.getMessage());
// 返回稍后重试
return ConsumeConcurrentlyStatus.RECONSUME_LATER;
}
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
// 启动消费者
consumer.start();
System.out.println("消费者启动成功");
// 保持运行
Thread.sleep(Long.MAX_VALUE);
}
/**
* 广播消费
*/
public static void broadcastConsume() throws Exception {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("broadcast-consumer-group");
consumer.setNamesrvAddr("localhost:9876");
// 设置为广播模式
consumer.setMessageModel(org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel.BROADCASTING);
consumer.subscribe("BroadcastTopic", "*");
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(
List<MessageExt> msgs,
ConsumeConcurrentlyContext context) {
for (MessageExt msg : msgs) {
System.out.printf(
"[广播消费] 消费者实例: %s, 消息: %s\n",
consumer.getClientIP(),
new String(msg.getBody())
);
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
System.out.println("广播消费者启动");
Thread.sleep(Long.MAX_VALUE);
}
private static void processMessage(String message) {
// 模拟业务处理
System.out.println("处理消息: " + message);
}
public static void main(String[] args) throws Exception {
concurrentConsume();
}
}
5.2 拉模式消费者
import org.apache.rocketmq.client.consumer.DefaultLitePullConsumer;
import org.apache.rocketmq.common.message.MessageExt;
import java.util.List;
/**
* 拉模式消费者 (Pull Consumer)
* 更灵活,可以精确控制消费速度和位置
*/
public class PullConsumer {
/**
* 手动拉取消息
*/
public static void litePull() throws Exception {
DefaultLitePullConsumer consumer = new DefaultLitePullConsumer("lite-pull-consumer-group");
consumer.setNamesrvAddr("localhost:9876");
// 订阅 Topic
consumer.subscribe("OrderTopic", "*");
// 启动消费者
consumer.start();
System.out.println("拉模式消费者启动");
try {
while (true) {
// 拉取消息 (超时时间 1秒)
List<MessageExt> messages = consumer.poll(1000);
if (messages.isEmpty()) {
System.out.println("没有新消息");
Thread.sleep(1000);
continue;
}
for (MessageExt msg : messages) {
String body = new String(msg.getBody(), RemotingHelper.DEFAULT_CHARSET);
System.out.printf(
"拉取消息 - MsgId: %s, QueueId: %d, Body: %s\n",
msg.getMsgId(),
msg.getQueueId(),
body
);
// 业务处理
processMessage(body);
}
// 手动提交消费位点
consumer.commitSync();
}
} finally {
consumer.shutdown();
}
}
/**
* 按队列拉取
*/
public static void pullByQueue() throws Exception {
DefaultLitePullConsumer consumer = new DefaultLitePullConsumer("queue-pull-consumer-group");
consumer.setNamesrvAddr("localhost:9876");
consumer.subscribe("OrderTopic", "*");
consumer.start();
try {
// 获取分配的队列
java.util.Collection<org.apache.rocketmq.common.message.MessageQueue> queues =
consumer.fetchMessageQueues("OrderTopic");
for (org.apache.rocketmq.common.message.MessageQueue queue : queues) {
System.out.println("处理队列: " + queue.getQueueId());
// 设置消费起始位置
consumer.seek(queue, 0);
// 拉取该队列的消息
while (true) {
List<MessageExt> messages = consumer.poll(1000);
if (messages.isEmpty()) {
break;
}
for (MessageExt msg : messages) {
if (msg.getQueueId() == queue.getQueueId()) {
System.out.printf(
"队列 %d - Offset: %d, Body: %s\n",
msg.getQueueId(),
msg.getQueueOffset(),
new String(msg.getBody())
);
}
}
}
}
} finally {
consumer.shutdown();
}
}
private static void processMessage(String message) {
System.out.println("处理消息: " + message);
}
public static void main(String[] args) throws Exception {
litePull();
}
}
6. 消息类型详解
6.1 延时消息
/**
* 延时消息
* 支持 18 个级别: 1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
*/
public class DelayMessageProducer {
public static void sendDelayMessage() throws Exception {
DefaultMQProducer producer = new DefaultMQProducer("delay-producer-group");
producer.setNamesrvAddr("localhost:9876");
producer.start();
try {
Message msg = new Message(
"DelayTopic",
"TagDelay",
"延时消息内容".getBytes(RemotingHelper.DEFAULT_CHARSET)
);
// 设置延时级别
// 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
msg.setDelayTimeLevel(3); // 延时 10 秒
SendResult result = producer.send(msg);
System.out.printf(
"延时消息发送成功 - MsgId: %s, 延时级别: %d\n",
result.getMsgId(),
msg.getDelayTimeLevel()
);
} finally {
producer.shutdown();
}
}
/**
* 延时消息应用场景:
* 1. 订单超时取消 (30分钟未支付)
* 2. 定时任务触发
* 3. 消息重试
*/
public static void orderTimeoutCancel() throws Exception {
DefaultMQProducer producer = new DefaultMQProducer("order-timeout-producer");
producer.setNamesrvAddr("localhost:9876");
producer.start();
try {
String orderId = "ORDER_001";
Message msg = new Message(
"OrderTimeoutTopic",
"timeout",
orderId.getBytes(RemotingHelper.DEFAULT_CHARSET)
);
// 30 分钟后检查订单状态
msg.setDelayTimeLevel(16); // 30分钟
SendResult result = producer.send(msg);
System.out.println("订单超时检查消息已发送: " + orderId);
} finally {
producer.shutdown();
}
}
public static void main(String[] args) throws Exception {
sendDelayMessage();
orderTimeoutCancel();
}
}
6.2 定时消息 (RocketMQ 5.0+)
/**
* 定时消息 (精确到毫秒)
* RocketMQ 5.0+ 支持
*/
public class TimerMessageProducer {
public static void sendTimerMessage() throws Exception {
DefaultMQProducer producer = new DefaultMQProducer("timer-producer-group");
producer.setNamesrvAddr("localhost:9876");
producer.start();
try {
Message msg = new Message(
"TimerTopic",
"TagTimer",
"定时消息".getBytes(RemotingHelper.DEFAULT_CHARSET)
);
// 设置投递时间 (10秒后)
long deliverTimeMs = System.currentTimeMillis() + 10000;
msg.setDeliverTimeMs(deliverTimeMs);
SendResult result = producer.send(msg);
System.out.printf(
"定时消息发送成功 - MsgId: %s, 投递时间: %s\n",
result.getMsgId(),
new java.util.Date(deliverTimeMs)
);
} finally {
producer.shutdown();
}
}
public static void main(String[] args) throws Exception {
sendTimerMessage();
}
}
7. 顺序消息
7.1 全局顺序消息
全局顺序消息
Topic: OrderTopic
└── Queue 0 (唯一队列)
├── Message 1
├── Message 2
├── Message 3
└── Message 4
特点:
- 一个 Topic 只有一个队列
- 生产者顺序发送
- 消费者顺序消费
- 性能受限
应用: 严格顺序场景 (少)
7.2 分区顺序消息
/**
* 分区顺序消息
* 同一订单的消息发送到同一队列,保证局部有序
*/
public class OrderlyMessageProducer {
/**
* 发送顺序消息
*/
public static void sendOrderlyMessage() throws Exception {
DefaultMQProducer producer = new DefaultMQProducer("orderly-producer-group");
producer.setNamesrvAddr("localhost:9876");
producer.start();
try {
// 模拟订单消息: 创建 -> 支付 -> 发货 -> 完成
String[] orders = {"ORDER_001", "ORDER_002", "ORDER_003"};
String[] steps = {"创建订单", "支付订单", "发货", "订单完成"};
for (String orderId : orders) {
for (String step : steps) {
Message msg = new Message(
"OrderlyTopic",
"TagOrderly",
(orderId + ":" + step).getBytes(RemotingHelper.DEFAULT_CHARSET)
);
// 发送到指定队列 (根据 orderId 哈希)
SendResult result = producer.send(msg,
new MessageQueueSelector() {
@Override
public MessageQueue select(
List<MessageQueue> mqs,
Message msg,
Object arg) {
// 根据订单 ID 选择队列
String orderId = (String) arg;
int index = Math.abs(orderId.hashCode()) % mqs.size();
return mqs.get(index);
}
},
orderId); // 传入 orderId 作为参数
System.out.printf(
"顺序消息发送 - OrderId: %s, Step: %s, QueueId: %d\n",
orderId,
step,
result.getMessageQueue().getQueueId()
);
Thread.sleep(100);
}
}
} finally {
producer.shutdown();
}
}
public static void main(String[] args) throws Exception {
sendOrderlyMessage();
}
}
/**
* 顺序消费者
*/
class OrderlyMessageConsumer {
public static void consumeOrderly() throws Exception {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("orderly-consumer-group");
consumer.setNamesrvAddr("localhost:9876");
consumer.subscribe("OrderlyTopic", "*");
// 注册顺序消费监听器
consumer.registerMessageListener(new MessageListenerOrderly() {
@Override
public ConsumeOrderlyStatus consumeMessage(
List<MessageExt> msgs,
ConsumeOrderlyContext context) {
// 设置自动提交
context.setAutoCommit(true);
for (MessageExt msg : msgs) {
String body = new String(msg.getBody());
System.out.printf(
"[顺序消费] QueueId: %d, Offset: %d, Body: %s\n",
msg.getQueueId(),
msg.getQueueOffset(),
body
);
}
return ConsumeOrderlyStatus.SUCCESS;
}
});
consumer.start();
System.out.println("顺序消费者启动");
Thread.sleep(Long.MAX_VALUE);
}
public static void main(String[] args) throws Exception {
consumeOrderly();
}
}
8. 事务消息
8.1 事务消息原理
事务消息流程
1. 发送半消息 (Half Message)
Producer ──▶ Broker (存储,对消费者不可见)
2. 执行本地事务
Producer ──▶ Local Transaction (DB 操作)
3. 提交/回滚事务
成功: Commit ──▶ Broker (消费者可见)
失败: Rollback ──▶ Broker (删除消息)
4. 事务回查 (如果2超时)
Broker ──▶ Producer (查询本地事务状态)
Producer ──▶ Broker (返回 COMMIT/ROLLBACK)
时序图:
Producer Broker Consumer
│ │ │
├─ 1.半消息 ─────▶│ │
│ │ │
├─ 2.本地事务 │ │
│ (DB操作) │ │
│ │ │
├─ 3.提交 ───────▶│ │
│ │ │
│ ├─ 4.消息可见 ──▶│
│ │ │
│◀─ 5.回查 ───────│ (超时未响应) │
│ │ │
├─ 6.返回状态 ───▶│ │
8.2 事务消息实现
import org.apache.rocketmq.client.producer.*;
import org.apache.rocketmq.common.message.Message;
/**
* 事务消息生产者
* 场景: 订单服务下单后发送消息给库存服务
*/
public class TransactionMessageProducer {
public static void sendTransactionMessage() throws Exception {
// 创建事务生产者
TransactionMQProducer producer = new TransactionMQProducer("transaction-producer-group");
producer.setNamesrvAddr("localhost:9876");
// 设置事务监听器
producer.setTransactionListener(new TransactionListener() {
/**
* 执行本地事务
*/
@Override
public LocalTransactionState executeLocalTransaction(
Message msg,
Object arg) {
String orderId = (String) arg;
System.out.println("执行本地事务 - OrderId: " + orderId);
try {
// 模拟本地事务 (订单入库)
boolean success = saveOrder(orderId);
if (success) {
System.out.println("本地事务执行成功,提交事务消息");
return LocalTransactionState.COMMIT_MESSAGE;
} else {
System.out.println("本地事务执行失败,回滚事务消息");
return LocalTransactionState.ROLLBACK_MESSAGE;
}
} catch (Exception e) {
System.err.println("本地事务异常: " + e.getMessage());
// 返回 UNKNOW,等待回查
return LocalTransactionState.UNKNOW;
}
}
/**
* 事务回查 (Broker 定期回查事务状态)
*/
@Override
public LocalTransactionState checkLocalTransaction(
MessageExt msg) {
String orderId = msg.getKeys();
System.out.println("事务回查 - OrderId: " + orderId);
// 查询本地事务执行结果
boolean exists = checkOrderExists(orderId);
if (exists) {
System.out.println("回查: 订单存在,提交消息");
return LocalTransactionState.COMMIT_MESSAGE;
} else {
System.out.println("回查: 订单不存在,回滚消息");
return LocalTransactionState.ROLLBACK_MESSAGE;
}
}
});
// 启动生产者
producer.start();
try {
for (int i = 0; i < 5; i++) {
String orderId = "ORDER_TX_" + i;
Message msg = new Message(
"TransactionTopic",
"TagTX",
orderId, // Keys (用于回查)
("订单数据:" + orderId).getBytes(RemotingHelper.DEFAULT_CHARSET)
);
// 发送事务消息
SendResult result = producer.sendMessageInTransaction(msg, orderId);
System.out.printf(
"事务消息发送 - MsgId: %s, Status: %s, OrderId: %s\n",
result.getMsgId(),
result.getSendStatus(),
orderId
);
Thread.sleep(1000);
}
// 等待回查
Thread.sleep(10000);
} finally {
producer.shutdown();
}
}
/**
* 模拟保存订单 (本地事务)
*/
private static boolean saveOrder(String orderId) {
// 模拟数据库操作
System.out.println("保存订单到数据库: " + orderId);
// 随机成功/失败
return Math.random() > 0.3;
}
/**
* 模拟查询订单是否存在
*/
private static boolean checkOrderExists(String orderId) {
// 模拟数据库查询
System.out.println("查询订单是否存在: " + orderId);
return true;
}
public static void main(String[] args) throws Exception {
sendTransactionMessage();
}
}
/**
* 事务消息消费者
*/
class TransactionMessageConsumer {
public static void consumeTransactionMessage() throws Exception {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("transaction-consumer-group");
consumer.setNamesrvAddr("localhost:9876");
consumer.subscribe("TransactionTopic", "*");
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(
List<MessageExt> msgs,
ConsumeConcurrentlyContext context) {
for (MessageExt msg : msgs) {
String orderId = msg.getKeys();
String body = new String(msg.getBody());
System.out.printf(
"消费事务消息 - OrderId: %s, Body: %s\n",
orderId,
body
);
// 执行业务逻辑 (如库存扣减)
deductInventory(orderId);
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
System.out.println("事务消息消费者启动");
Thread.sleep(Long.MAX_VALUE);
}
private static void deductInventory(String orderId) {
System.out.println("扣减库存: " + orderId);
}
public static void main(String[] args) throws Exception {
consumeTransactionMessage();
}
}
9. 消息过滤
9.1 Tag 过滤
/**
* Tag 过滤
*/
public class TagFilterExample {
/**
* 生产者发送带 Tag 的消息
*/
public static void sendWithTag() throws Exception {
DefaultMQProducer producer = new DefaultMQProducer("tag-producer-group");
producer.setNamesrvAddr("localhost:9876");
producer.start();
try {
String[] tags = {"VIP", "NORMAL", "GUEST"};
for (int i = 0; i < 30; i++) {
String tag = tags[i % tags.length];
Message msg = new Message(
"UserTopic",
tag, // Tag
("User_" + i + "_" + tag).getBytes(RemotingHelper.DEFAULT_CHARSET)
);
producer.send(msg);
System.out.println("发送消息 - Tag: " + tag + ", User: User_" + i);
}
} finally {
producer.shutdown();
}
}
/**
* 消费者按 Tag 过滤
*/
public static void consumeByTag() throws Exception {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("tag-consumer-group");
consumer.setNamesrvAddr("localhost:9876");
// 订阅 VIP 和 NORMAL 用户消息
consumer.subscribe("UserTopic", "VIP || NORMAL");
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(
List<MessageExt> msgs,
ConsumeConcurrentlyContext context) {
for (MessageExt msg : msgs) {
System.out.printf(
"消费消息 - Tag: %s, Body: %s\n",
msg.getTags(),
new String(msg.getBody())
);
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
System.out.println("Tag 过滤消费者启动");
Thread.sleep(Long.MAX_VALUE);
}
public static void main(String[] args) throws Exception {
sendWithTag();
// consumeByTag();
}
}
9.2 SQL92 过滤
/**
* SQL92 过滤 (更灵活的过滤)
* 需要在 Broker 配置: enablePropertyFilter=true
*/
public class SQLFilterExample {
/**
* 发送带属性的消息
*/
public static void sendWithProperties() throws Exception {
DefaultMQProducer producer = new DefaultMQProducer("sql-producer-group");
producer.setNamesrvAddr("localhost:9876");
producer.start();
try {
for (int i = 0; i < 30; i++) {
Message msg = new Message(
"OrderTopic",
"TagOrder",
("Order_" + i).getBytes(RemotingHelper.DEFAULT_CHARSET)
);
// 设置消息属性
msg.putUserProperty("region", i % 3 == 0 ? "华东" : "华北");
msg.putUserProperty("amount", String.valueOf(100 + i * 10));
msg.putUserProperty("vip", i % 5 == 0 ? "true" : "false");
producer.send(msg);
System.out.printf(
"发送订单 - Order: %d, Region: %s, Amount: %d, VIP: %s\n",
i,
msg.getUserProperty("region"),
Integer.parseInt(msg.getUserProperty("amount")),
msg.getUserProperty("vip")
);
}
} finally {
producer.shutdown();
}
}
/**
* SQL92 过滤消费
*/
public static void consumeWithSQL() throws Exception {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("sql-consumer-group");
consumer.setNamesrvAddr("localhost:9876");
// SQL92 过滤表达式
// 过滤条件: 华东地区 AND 金额 > 200 AND VIP 用户
String sql = "region = '华东' AND amount > 200 AND vip = 'true'";
consumer.subscribe("OrderTopic",
org.apache.rocketmq.client.consumer.MessageSelector.bySql(sql));
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(
List<MessageExt> msgs,
ConsumeConcurrentlyContext context) {
for (MessageExt msg : msgs) {
System.out.printf(
"SQL过滤消费 - Body: %s, Region: %s, Amount: %s, VIP: %s\n",
new String(msg.getBody()),
msg.getUserProperty("region"),
msg.getUserProperty("amount"),
msg.getUserProperty("vip")
);
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
System.out.println("SQL 过滤消费者启动");
Thread.sleep(Long.MAX_VALUE);
}
public static void main(String[] args) throws Exception {
sendWithProperties();
// consumeWithSQL();
}
}
10. 死信队列与消息重试
10.1 消息重试机制
消息重试机制
消费失败 ──▶ 重试 1 (10s后)
│
├──▶ 重试 2 (30s后)
│
├──▶ 重试 3 (1m后)
│
├──▶ ... (最多16次)
│
└──▶ 死信队列 (DLQ)
重试间隔: 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
死信队列命名: %DLQ%ConsumerGroupName
/**
* 消息重试与死信队列
*/
public class RetryAndDLQExample {
/**
* 消费者处理失败,触发重试
*/
public static void consumerWithRetry() throws Exception {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("retry-consumer-group");
consumer.setNamesrvAddr("localhost:9876");
consumer.subscribe("RetryTopic", "*");
// 设置最大重试次数 (默认 16 次)
consumer.setMaxReconsumeTimes(3);
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(
List<MessageExt> msgs,
ConsumeConcurrentlyContext context) {
for (MessageExt msg : msgs) {
String body = new String(msg.getBody());
int reconsumeTimes = msg.getReconsumeTimes();
System.out.printf(
"消费消息 - Body: %s, 重试次数: %d\n",
body,
reconsumeTimes
);
// 模拟消费失败
if (reconsumeTimes < 2) {
System.out.println("消费失败,等待重试...");
return ConsumeConcurrentlyStatus.RECONSUME_LATER;
}
System.out.println("消费成功");
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
System.out.println("重试消费者启动");
Thread.sleep(Long.MAX_VALUE);
}
/**
* 死信队列消费者
*/
public static void consumeDLQ() throws Exception {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("dlq-consumer-group");
consumer.setNamesrvAddr("localhost:9876");
// 订阅死信队列 (命名规则: %DLQ% + Consumer Group)
consumer.subscribe("%DLQ%retry-consumer-group", "*");
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(
List<MessageExt> msgs,
ConsumeConcurrentlyContext context) {
for (MessageExt msg : msgs) {
System.out.printf(
"死信队列消息 - MsgId: %s, Body: %s, " +
"原Topic: %s, 重试次数: %d\n",
msg.getMsgId(),
new String(msg.getBody()),
msg.getProperty("ORIGIN_TOPIC"),
msg.getReconsumeTimes()
);
// 特殊处理死信消息
handleDLQMessage(msg);
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
System.out.println("死信队列消费者启动");
Thread.sleep(Long.MAX_VALUE);
}
private static void handleDLQMessage(MessageExt msg) {
// 1. 记录到数据库
// 2. 发送告警
// 3. 人工介入处理
System.out.println("特殊处理死信消息: " + msg.getMsgId());
}
public static void main(String[] args) throws Exception {
// consumerWithRetry();
consumeDLQ();
}
}
11. Spring Boot 集成
11.1 配置文件
# application.yml
rocketmq:
# NameServer 地址
name-server: localhost:9876
# 生产者配置
producer:
group: spring-producer-group
send-message-timeout: 3000
compress-message-body-threshold: 4096
max-message-size: 4194304
retry-times-when-send-failed: 2
retry-times-when-send-async-failed: 2
retry-next-server: false
# 消费者配置
consumer:
group: spring-consumer-group
consume-thread-min: 20
consume-thread-max: 64
consume-timeout: 15
11.2 生产者使用
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
/**
* Spring Boot RocketMQ 生产者
*/
@Service
public class OrderService {
@Autowired
private RocketMQTemplate rocketMQTemplate;
/**
* 同步发送
*/
public void createOrder(Order order) {
// 同步发送
rocketMQTemplate.syncSend("OrderTopic", order);
System.out.println("订单创建: " + order.getOrderId());
}
/**
* 异步发送
*/
public void createOrderAsync(Order order) {
rocketMQTemplate.asyncSend("OrderTopic", order, new org.apache.rocketmq.spring.core.RocketMQLocalTransactionListener() {
@Override
public org.apache.rocketmq.spring.core.RocketMQLocalTransactionState executeLocalTransaction(
org.springframework.messaging.Message msg, Object arg) {
System.out.println("异步发送成功: " + order.getOrderId());
return org.apache.rocketmq.spring.core.RocketMQLocalTransactionState.COMMIT;
}
@Override
public org.apache.rocketmq.spring.core.RocketMQLocalTransactionState checkLocalTransaction(
org.springframework.messaging.Message msg) {
return org.apache.rocketmq.spring.core.RocketMQLocalTransactionState.COMMIT;
}
});
}
/**
* 发送带 Tag 的消息
*/
public void createVIPOrder(Order order) {
rocketMQTemplate.syncSend("OrderTopic:VIP", order);
}
/**
* 发送延时消息
*/
public void createOrderWithDelay(Order order, int delayLevel) {
rocketMQTemplate.syncSend(
"OrderTopic",
MessageBuilder.withPayload(order).build(),
3000,
delayLevel
);
}
/**
* 发送顺序消息
*/
public void createOrderlyMessage(Order order) {
rocketMQTemplate.syncSendOrderly(
"OrderTopic",
order,
order.getOrderId() // 根据 orderId 选择队列
);
}
/**
* 发送事务消息
*/
public void createOrderWithTransaction(Order order) {
rocketMQTemplate.sendMessageInTransaction(
"OrderTopic",
MessageBuilder.withPayload(order).build(),
order
);
}
}
/**
* 订单实体
*/
@Data
class Order {
private String orderId;
private String userId;
private Double amount;
private String status;
}
11.3 消费者使用
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;
/**
* Spring Boot RocketMQ 消费者
*/
@Component
@RocketMQMessageListener(
topic = "OrderTopic",
consumerGroup = "order-consumer-group",
selectorExpression = "*" // Tag 过滤表达式
)
public class OrderConsumer implements RocketMQListener<Order> {
@Override
public void onMessage(Order order) {
System.out.println("收到订单消息: " + order.getOrderId());
processOrder(order);
}
private void processOrder(Order order) {
// 业务处理
System.out.println("处理订单: " + order);
}
}
/**
* 并发消费监听器
*/
@Component
@RocketMQMessageListener(
topic = "ConcurrentTopic",
consumerGroup = "concurrent-consumer-group",
consumeMode = org.apache.rocketmq.spring.annotation.ConsumeMode.CONCURRENTLY,
consumeThreadMax = 20
)
public class ConcurrentConsumer implements RocketMQListener<String> {
@Override
public void onMessage(String message) {
System.out.println("并发消费: " + message);
}
}
/**
* 顺序消费监听器
*/
@Component
@RocketMQMessageListener(
topic = "OrderlyTopic",
consumerGroup = "orderly-consumer-group",
consumeMode = org.apache.rocketmq.spring.annotation.ConsumeMode.ORDERLY
)
public class OrderlyConsumer implements RocketMQListener<String> {
@Override
public void onMessage(String message) {
System.out.println("顺序消费: " + message);
}
}
/**
* 广播消费监听器
*/
@Component
@RocketMQMessageListener(
topic = "BroadcastTopic",
consumerGroup = "broadcast-consumer-group",
messageModel = org.apache.rocketmq.spring.annotation.MessageModel.BROADCASTING
)
public class BroadcastConsumer implements RocketMQListener<String> {
@Override
public void onMessage(String message) {
System.out.println("广播消费: " + message);
}
}
12. 生产环境最佳实践
12.1 消息发送最佳实践
/**
* 生产环境最佳实践
*/
public class ProductionBestPractices {
/**
* 1. 设置合理的超时时间
*/
public static void configurateTimeout() {
DefaultMQProducer producer = new DefaultMQProducer("prod-group");
producer.setSendMsgTimeout(3000); // 发送超时 3秒
producer.setRetryTimesWhenSendFailed(2); // 同步发送失败重试 2次
}
/**
* 2. 合理使用发送方式
* - 可靠性要求高: 同步发送
* - 性能要求高: 异步发送
* - 日志类场景: 单向发送
*/
public static void chooseSendMethod() {
// 关键业务: 同步
// SendResult result = producer.send(msg);
// 一般业务: 异步
// producer.send(msg, callback);
// 日志收集: 单向
// producer.sendOneway(msg);
}
/**
* 3. 设置消息 Key (方便追踪)
*/
public static Message createMessageWithKey(String orderId) {
Message msg = new Message("OrderTopic", "order_body".getBytes());
msg.setKeys(orderId); // 设置 Key
return msg;
}
/**
* 4. 合理设置 Tag (消费端过滤)
*/
public static void useTag() {
// 不同类型消息使用不同 Tag
// VIP订单: VIP
// 普通订单: NORMAL
// 积分订单: POINTS
}
/**
* 5. 批量发送优化
*/
public static void batchSendOptimization() {
// 批量发送,减少网络开销
// 但要控制批量大小 (不超过 1MB)
}
/**
* 6. 消息幂等性处理
*/
public static void handleIdempotent() {
// 消费端通过唯一 Key 去重
// 可使用 Redis/数据库记录已处理的 MsgId
}
/**
* 7. 异常处理
*/
public static void handleException() {
try {
// producer.send(msg);
} catch (Exception e) {
// 1. 记录日志
// 2. 告警通知
// 3. 落库重试
System.err.println("发送失败: " + e.getMessage());
}
}
}
12.2 消息消费最佳实践
/**
* 消费端最佳实践
*/
public class ConsumerBestPractices {
/**
* 1. 消费幂等性保证
*/
public static boolean consumeWithIdempotent(MessageExt msg) {
String msgId = msg.getMsgId();
// 检查是否已处理
if (isProcessed(msgId)) {
System.out.println("消息已处理,跳过: " + msgId);
return true;
}
// 处理业务
processMessage(msg);
// 标记已处理
markProcessed(msgId);
return true;
}
/**
* 2. 消费失败重试策略
*/
public static ConsumeConcurrentlyStatus consumeWithRetry(
MessageExt msg, int maxRetry) {
int retryTimes = msg.getReconsumeTimes();
if (retryTimes >= maxRetry) {
// 达到最大重试次数,记录到数据库人工处理
saveToFailedTable(msg);
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
try {
processMessage(msg);
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
} catch (Exception e) {
System.err.println("消费失败,重试: " + retryTimes);
return ConsumeConcurrentlyStatus.RECONSUME_LATER;
}
}
/**
* 3. 消费线程数配置
*/
public static void configurateThreads() {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group");
// 根据业务复杂度和处理时间配置
consumer.setConsumeThreadMin(20);
consumer.setConsumeThreadMax(64);
}
/**
* 4. 消费超时配置
*/
public static void configurateTimeout() {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group");
// 消费超时时间 (分钟)
consumer.setConsumeTimeout(15);
}
/**
* 5. 批量消费优化
*/
public static void batchConsume() {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group");
// 每次拉取的最大消息数
consumer.setConsumeMessageBatchMaxSize(10);
// 批量处理可以提高效率
// 但要注意单次处理时间不要太长
}
private static boolean isProcessed(String msgId) {
// 查询 Redis/数据库
return false;
}
private static void processMessage(MessageExt msg) {
System.out.println("处理消息: " + msg.getMsgId());
}
private static void markProcessed(String msgId) {
// 写入 Redis/数据库
}
private static void saveToFailedTable(MessageExt msg) {
System.out.println("保存到失败表: " + msg.getMsgId());
}
}
12.3 集群部署建议
生产环境部署架构
最小配置 (单机房):
┌────────────────────────────────────┐
│ NameServer Cluster (3节点) │
├────────────────────────────────────┤
│ - 192.168.1.10:9876 │
│ - 192.168.1.11:9876 │
│ - 192.168.1.12:9876 │
└────────────────────────────────────┘
│
▼
┌────────────────────────────────────┐
│ Broker Cluster (主从模式) │
├────────────────────────────────────┤
│ Master (192.168.1.20) │
│ ├─ Slave (192.168.1.21) │
│ └─ Slave (192.168.1.22) │
└────────────────────────────────────┘
推荐配置:
1. NameServer: 3个节点 (保证高可用)
2. Broker: 主从模式 (1主2从)
3. 磁盘: SSD (提升性能)
4. 内存: 16GB+ (根据消息量)
5. CPU: 8核+ (根据 TPS)
配置建议:
- flushDiskType: ASYNC_FLUSH (异步刷盘,性能高)
- brokerRole: ASYNC_MASTER (异步复制)
- deleteWhen: 04 (凌晨4点删除过期文件)
- fileReservedTime: 48 (保留48小时)
13. 监控与运维
13.1 关键监控指标
RocketMQ 监控指标
生产者指标:
├─ 发送 TPS
├─ 发送成功率
├─ 发送延迟
└─ 发送失败次数
消费者指标:
├─ 消费 TPS
├─ 消息堆积量
├─ 消费延迟
└─ 消费失败次数
Broker 指标:
├─ 磁盘使用率
├─ CPU 使用率
├─ 内存使用率
├─ 网络 I/O
└─ JVM GC 频率
告警阈值:
┌──────────────────────┬──────────────┐
│ Metric │ Threshold │
├──────────────────────┼──────────────┤
│ 消息堆积量 │ > 10万 │
│ 消费延迟 │ > 1分钟 │
│ 磁盘使用率 │ > 80% │
│ 发送失败率 │ > 1% │
└──────────────────────┴──────────────┘
13.2 运维命令
# 查看集群状态
sh mqadmin clusterList -n localhost:9876
# 查看 Topic 信息
sh mqadmin topicStatus -t OrderTopic -n localhost:9876
# 查看消费者组信息
sh mqadmin consumerProgress -g order-consumer-group -n localhost:9876
# 查看消息堆积
sh mqadmin consumerProgress -g order-consumer-group -n localhost:9876 | grep Diff
# 创建 Topic
sh mqadmin updateTopic -t NewTopic -n localhost:9876 -c DefaultCluster
# 删除 Topic
sh mqadmin deleteTopic -t OldTopic -n localhost:9876 -c DefaultCluster
# 查询消息
sh mqadmin queryMsgById -i msgId -n localhost:9876
# 按 Key 查询消息
sh mqadmin queryMsgByKey -t OrderTopic -k ORDER_001 -n localhost:9876
# 重置消费位点
sh mqadmin resetOffsetByTime -g order-consumer-group -t OrderTopic -s "2024-01-01 00:00:00" -n localhost:9876
13.3 控制台监控
/**
* 自定义监控指标收集
*/
@Component
public class RocketMQMonitor {
@Autowired
private DefaultMQPushConsumer consumer;
/**
* 获取消费者统计信息
*/
public void printConsumerStats() {
org.apache.rocketmq.client.consumer.rebalance.AllocateMessageQueueAveragely strategy =
new org.apache.rocketmq.client.consumer.rebalance.AllocateMessageQueueAveragely();
// 获取消费进度
try {
java.util.Map<org.apache.rocketmq.common.message.MessageQueue, Long> offsetTable =
consumer.getOffsetStore().cloneOffsetTable("OrderTopic");
for (java.util.Map.Entry<org.apache.rocketmq.common.message.MessageQueue, Long> entry :
offsetTable.entrySet()) {
System.out.printf(
"Queue: %d, Offset: %d\n",
entry.getKey().getQueueId(),
entry.getValue()
);
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 定时采集监控指标
*/
@Scheduled(fixedRate = 60000) // 每分钟采集一次
public void collectMetrics() {
// 采集消费进度
// 采集堆积量
// 上报到监控系统 (Prometheus/Grafana)
}
}
14. 总结
14.1 RocketMQ 核心优势
RocketMQ 核心优势
1. 高性能 ★★★★★
- 单机万级 TPS
- 集群百万级 TPS
- 毫秒级延迟
2. 高可用 ★★★★★
- 主从架构
- 自动故障切换
- 数据不丢失
3. 功能丰富 ★★★★★
- 顺序消息
- 事务消息
- 延时消息
- 消息过滤
- 死信队列
4. 可运维性 ★★★★★
- 控制台管理
- 完善的监控
- 丰富的运维工具
5. 生态完善 ★★★★★
- Spring Boot 集成
- 多语言客户端
- 活跃的社区
14.2 最佳实践总结
-
消息发送
- 同步发送保证可靠性
- 异步发送提升性能
- 设置合理的超时和重试
- 使用 Key 方便消息追踪
-
消息消费
- 保证消费幂等性
- 合理配置线程数
- 及时处理死信消息
- 监控消息堆积
-
集群部署
- NameServer 至少 3 节点
- Broker 主从模式部署
- 使用 SSD 磁盘
- 配置合理的刷盘策略
-
监控运维
- 监控关键指标
- 及时告警
- 定期备份配置
- 制定应急预案
14.3 学习资源
- 官方文档: https://rocketmq.apache.org/docs
- GitHub: https://github.com/apache/rocketmq
- 控制台: https://github.com/apache/rocketmq-dashboard
- 中文社区: https://github.com/alibaba/spring-cloud-alibaba
3617

被折叠的 条评论
为什么被折叠?



