RocketMQ 实战指南

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 最佳实践总结

  1. 消息发送

    • 同步发送保证可靠性
    • 异步发送提升性能
    • 设置合理的超时和重试
    • 使用 Key 方便消息追踪
  2. 消息消费

    • 保证消费幂等性
    • 合理配置线程数
    • 及时处理死信消息
    • 监控消息堆积
  3. 集群部署

    • NameServer 至少 3 节点
    • Broker 主从模式部署
    • 使用 SSD 磁盘
    • 配置合理的刷盘策略
  4. 监控运维

    • 监控关键指标
    • 及时告警
    • 定期备份配置
    • 制定应急预案

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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值