Skip to content

RocketMQ 详解:分布式消息队列 ​

  RocketMQ 是阿里巴巴开源的分布式消息中间件,经过历年双十一的考验,具备低延迟、高吞吐、高可靠、海量消息堆积等特性。在 Spring Cloud Alibaba 生态中,RocketMQ 承担着异步解耦、流量削峰、最终一致性等关键职责。

一、为什么需要消息队列? ​

  在单体架构中,所有业务逻辑都在一个进程内完成,方法调用是同步的、即时的。但当系统演进到分布式架构后,服务之间需要通过网络通信,同步调用会带来严重的耦合问题和性能瓶颈。消息队列正是为了解决这些问题而生的。

1.1 三大核心价值 ​

价值核心思路一句话
异步解耦非关键流程异步化,不阻塞主流程"不重要的先记下来,回头慢慢做"
流量削峰瞬时高并发请求先入队,后端平滑消费"洪水来了先蓄水,下游慢慢放"
最终一致性事务消息保证分布式事务的最终一致性"先记个账,办成了通知你,失败了回滚"

1.2 场景一:异步处理 ​

  场景:用户下单后,需要发送短信通知、推送 App 消息、增加积分。如果同步处理,整个流程耗时 = 下单 + 发短信 + 推送 + 加积分,用户体验极差。

没有消息队列时(同步调用):

用户下单
  │
  ├── 1. 创建订单(100ms)
  ├── 2. 发送短信通知(200ms)    ← 用户干等着
  ├── 3. 推送 App 消息(150ms)   ← 用户干等着
  └── 4. 增加积分(100ms)        ← 用户干等着
                              ─────
                              总耗时:550ms,用户体验差

有了消息队列后(异步解耦):

用户下单
  │
  └── 1. 创建订单(100ms)→ 发送消息到 MQ → 返回"下单成功"(100ms)
                              │
                              ├── 短信服务消费消息 → 发送短信(200ms)
                              ├── 推送服务消费消息 → 推送消息(150ms)
                              └── 积分服务消费消息 → 增加积分(100ms)

  用户感知耗时:100ms,其余操作异步完成,不阻塞主流程

1.3 场景二:流量削峰 ​

  场景:秒杀活动,瞬时 10 万 QPS,后端数据库只能扛 3000 QPS。如果不做削峰,数据库瞬间崩溃,所有请求都失败。

没有消息队列时:

秒杀开始
  100000 QPS  ──────────────►  数据库(3000 QPS)
                                │
                                └── 崩溃!所有请求失败

有了消息队列后:

秒杀开始
  100000 QPS  ──►  [ RocketMQ 队列 ]  ──►  消费者(3000 QPS)
                   │                        │
                   │  消息堆积在队列中,     │  消费者按自己的节奏消费
                   │  不丢失,慢慢消费       │  数据库压力可控
                   │                        │
                   └── 70,000 条消息        └── 每秒处理 3000 条
                       排队等待消费              约 23 秒处理完

1.4 场景三:系统解耦 ​

  场景:订单服务创建订单后,需要通知库存服务扣减库存。如果通过 RPC 同步调用,订单服务必须知道库存服务的地址,且库存服务挂了会直接影响下单。

没有消息队列时(强耦合):

订单服务 ──RPC 调用──► 库存服务
  │                      │
  │  库存服务挂了?       │  需要硬编码库存服务地址
  │  扣库存失败?         │  新增下游服务需要改订单服务代码
  │  全部影响下单!       │

有了消息队列后(异步解耦):

订单服务 ──发送消息──►  [ RocketMQ Topic: ORDER_CREATED ]
                            │
                            ├── 库存服务消费 → 扣减库存
                            ├── 物流服务消费 → 创建运单
                            └── 风控服务消费 → 风险评估

  订单服务只管发消息,不需要知道谁在消费
  新增下游消费者,只需订阅 Topic,订单服务无需任何改动

二、核心概念 ​

  理解 RocketMQ 的核心概念是正确使用它的前提。下面逐一介绍每个核心角色。

2.1 核心角色一览 ​

角色英文职责类比
生产者Producer发送消息快递发件人
消费者Consumer接收并处理消息快递收件人
消息代理Broker存储和转发消息快递中转站
路由注册NameServer维护 Broker 路由信息快递网点查询系统
主题Topic消息的逻辑分类快递类型(文件/包裹/冷链)
标签TagTopic 下的二级分类快递子类型(加急/普通)
队列Queue消息的物理分区快递分拣传送带
消费者组ConsumerGroup逻辑上相同的消费者集合同一个收件部门的成员

2.2 各概念详解 ​

Producer(生产者)

生产者负责生产消息,将消息发送到 Broker。生产者不需要知道消费者是谁,只管把消息发到指定的 Topic。RocketMQ 支持三种发送方式:

  • 同步发送:发送后等待 Broker 确认,可靠性最高,但有延迟
  • 异步发送:发送后立即返回,通过回调处理结果,吞吐量高
  • 单向发送:发送后不关心结果,性能最高,但可能丢失消息

Consumer(消费者)

消费者从 Broker 拉取消息并进行业务处理。RocketMQ 支持两种消费模式(详见第五章):

  • 集群消费:同一条消息只被 ConsumerGroup 中的一个消费者消费
  • 广播消费:同一条消息被 ConsumerGroup 中的每一个消费者消费

Broker(消息代理)

Broker 是 RocketMQ 的核心,负责消息的存储、转发和投递。一个 Broker 可以包含多个 Topic,每个 Topic 又分为多个 Queue。Broker 通常以主从模式部署,主节点负责读写,从节点负责备份。

NameServer(路由注册)

NameServer 是 RocketMQ 的路由注册中心,维护着 Broker 的地址和 Topic 路由信息。NameServer 之间互不通信,每个 NameServer 节点都独立维护完整的路由信息。Producer 和 Consumer 从 NameServer 获取路由信息后,直接与 Broker 通信。

Topic(主题)

Topic 是消息的逻辑分类,类似于数据库中的"表"。生产者和消费者通过 Topic 进行关联:生产者往某个 Topic 发消息,消费者从某个 Topic 收消息。一个 Topic 可以跨越多个 Broker。

Tag(标签)

Tag 是 Topic 下的二级分类,用于消息过滤。比如 Topic 是"订单",Tag 可以是"订单创建"、"订单支付"、"订单取消"。消费者可以订阅 Topic 下的某些 Tag,只消费关心的消息。

Queue(队列)

Queue 是消息的物理分区,RocketMQ 中一个 Topic 对应多个 Queue,Queue 是消息存储和消费的最小单元。Queue 数量在创建 Topic 时指定,一般建议与消费者数量匹配。每个 Queue 中的消息严格有序。

ConsumerGroup(消费者组)

ConsumerGroup 是一组逻辑上相同的消费者,它们共同消费同一个 Topic。在集群消费模式下,同一 ConsumerGroup 内的消费者分摊 Queue:一个 Queue 只能被组内一个消费者消费,但一个消费者可以消费多个 Queue。

2.3 概念关系图 ​

┌─────────────────────────────────────────────────────────────────────┐
│                          NameServer 集群                              │
│         ┌──────────┐    ┌──────────┐    ┌──────────┐                │
│         │   NS-1   │    │   NS-2   │    │   NS-3   │                │
│         │  (独立)   │    │  (独立)   │    │  (独立)   │                │
│         └────┬─────┘    └────┬─────┘    └────┬─────┘                │
│              │               │               │                       │
│         注册路由信息      注册路由信息      注册路由信息                 │
└──────────────┼───────────────┼───────────────┼───────────────────────┘
               │               │               │
               ▼               ▼               ▼
┌─────────────────────────────────────────────────────────────────────┐
│                           Broker 集群                                 │
│                                                                      │
│  ┌──────────────────────┐          ┌──────────────────────┐         │
│  │    Broker-A (Master)  │  ◄──►   │   Broker-A (Slave)    │         │
│  │                        │ 主从复制  │                        │         │
│  │  Topic: Order          │          │  Topic: Order          │         │
│  │  ┌────┐ ┌────┐ ┌────┐ │          │  (只读备份)             │         │
│  │  │ Q0 │ │ Q1 │ │ Q2 │ │          │                        │         │
│  │  └────┘ └────┘ └────┘ │          │                        │         │
│  └──────────────────────┘          └──────────────────────┘         │
│                                                                      │
│  ┌──────────────────────┐          ┌──────────────────────┐         │
│  │    Broker-B (Master)  │  ◄──►   │   Broker-B (Slave)    │         │
│  │  Topic: Order          │ 主从复制  │  Topic: Order          │         │
│  │  ┌────┐ ┌────┐ ┌────┐ │          │  (只读备份)             │         │
│  │  │ Q3 │ │ Q4 │ │ Q5 │ │          │                        │         │
│  │  └────┘ └────┘ └────┘ │          │                        │         │
│  └──────────────────────┘          └──────────────────────┘         │
└─────────────────────────────────────────────────────────────────────┘
               ▲                               ▲
               │  发送消息                       │  拉取消息
               │                               │
┌──────────────┴───────────┐   ┌───────────────┴──────────┐
│     Producer 集群          │   │     ConsumerGroup 集群    │
│                            │   │                          │
│  ┌────────┐ ┌────────┐    │   │  ┌────────┐ ┌────────┐   │
│  │  P-1   │ │  P-2   │    │   │  │  C-1   │ │  C-2   │   │
│  └────────┘ └────────┘    │   │  │ (Q0,Q1)│ │ (Q2,Q3)│   │
│                            │   │  └────────┘ └────────┘   │
│  发送到 Topic: Order       │   │  消费 Topic: Order        │
│  可按 Tag: pay/create      │   │  Tag: pay (只消费支付消息) │
└────────────────────────────┘   └──────────────────────────┘

三、核心架构 ​

3.1 整体架构 ​

┌──────────────────────────────────────────────────────────────────────┐
│                           NameServer 集群                              │
│                                                                       │
│   ┌──────────┐    ┌──────────┐    ┌──────────┐                       │
│   │   NS-1   │    │   NS-2   │    │   NS-3   │    ← 无状态,互不通信   │
│   │          │    │          │    │          │                        │
│   │ RouteInfo│    │ RouteInfo│    │ RouteInfo│    ← 每台独立维护全量路由 │
│   └────┬─────┘    └────┬─────┘    └────┬─────┘                       │
│        │               │               │                              │
│        │    Broker 向所有 NameServer 注册(每 30s 心跳)                 │
│        │               │               │                              │
└────────┼───────────────┼───────────────┼──────────────────────────────┘
         │               │               │
         ▼               ▼               ▼
┌──────────────────────────────────────────────────────────────────────┐
│                           Broker 集群                                  │
│                                                                       │
│   ┌──────────────────────┐     ┌──────────────────────┐              │
│   │   Broker-Master-A    │     │   Broker-Master-B    │              │
│   │                      │     │                      │              │
│   │   Topic: Order       │     │   Topic: Order       │              │
│   │   Queue: 0, 1, 2, 3  │     │   Queue: 4, 5, 6, 7  │              │
│   │   (读写)              │     │   (读写)              │              │
│   └──────────┬───────────┘     └──────────┬───────────┘              │
│              │ 主从同步                      │ 主从同步                   │
│              ▼                              ▼                          │
│   ┌──────────────────────┐     ┌──────────────────────┐              │
│   │   Broker-Slave-A     │     │   Broker-Slave-B     │              │
│   │   (只读备份)          │     │   (只读备份)          │              │
│   └──────────────────────┘     └──────────────────────┘              │
└──────────────────────────────────────────────────────────────────────┘
         ▲                              ▲
         │ 发送消息                       │ 拉取消息
         │                              │
┌────────┴────────┐            ┌────────┴────────┐
│  Producer 集群    │            │  Consumer 集群    │
│                  │            │                  │
│  ┌────┐ ┌────┐   │            │  ┌────┐ ┌────┐   │
│  │ P1 │ │ P2 │   │            │  │ C1 │ │ C2 │   │
│  └────┘ └────┘   │            │  └────┘ └────┘   │
└──────────────────┘            └──────────────────┘

3.2 消息路由流程 ​

Producer 发送消息的完整流程:

  1. Producer 启动,从 NameServer 拉取 Topic 路由信息
     ┌──────────┐     ① 获取路由信息         ┌──────────┐
     │ Producer │ ──────────────────────────► │NameServer│
     └──────────┘ ◄────────────────────────── │          │
                    返回:Broker 列表 + Queue 列表  └──────────┘

  2. Producer 根据路由信息,选择 Queue 发送消息
     ┌──────────┐     ② 直接发送消息           ┌──────────┐
     │ Producer │ ──────────────────────────► │  Broker  │
     │          │                              │  (Master)│
     │ 轮询选择  │                              │  Queue-0 │
     │ Queue    │                              │  Queue-1 │
     └──────────┘                              └──────────┘

  3. 路由信息定期更新(每 30s),感知 Broker 上下线
     ┌──────────┐     ③ 定时更新路由           ┌──────────┐
     │ Producer │ ──────────────────────────► │NameServer│
     └──────────┘                              └──────────┘

Consumer 消费消息的流程:

  1. Consumer 启动,从 NameServer 获取路由信息
  2. Consumer 根据负载均衡策略,分配 Queue
  3. Consumer 从分配的 Queue 拉取消息(Pull 模式)
  4. 消息处理完成后返回消费结果
  5. 路由信息定期更新(每 30s),感知 Queue 变化

3.3 为什么 NameServer 不用 Zookeeper? ​

  很多分布式系统选择 Zookeeper 做注册中心,RocketMQ 却自己实现了 NameServer。这不是重复造轮子,而是有明确的技术考量。

对比维度ZookeeperNameServer
CAP 选择CP(强一致性)AP(高可用性)
一致性协议ZAB(Paxos 变种)无,各节点独立
节点通信节点间心跳 + 选举互不通信,完全对等
故障影响Leader 选举期间不可用(数十秒)单节点故障无影响,其他节点继续服务
运维复杂度需要关注 Leader 选举、脑裂、数据一致性极其简单,无状态,随意扩缩容
数据存储持久化到磁盘全内存,Broker 重启后重新注册
适用场景需要强一致性的配置管理轻量级路由发现,可容忍短暂不一致

RocketMQ 选择 NameServer 的核心原因:

  1. 轻量级:NameServer 只做路由发现,不涉及复杂的选举和一致性协议,代码量少,维护成本低
  2. 高可用(AP):对于消息队列来说,NameServer 不可用比路由信息不一致更致命。NameServer 集群中任意节点挂了,其他节点继续服务,不会像 Zookeeper 那样在 Leader 选举期间不可用
  3. 最终一致性可接受:Broker 上下线不是高频事件,30s 内路由信息不一致是完全可以接受的。即使路由信息暂时不一致,Producer 也有重试机制兜底
  4. 运维简单:不需要维护 Zookeeper 集群,不需要关注选举、脑裂、数据一致性等复杂问题

四、消息类型 ​

  RocketMQ 支持四种消息类型,覆盖了从普通异步通知到分布式事务的完整场景需求。

4.1 普通消息 ​

  普通消息是最基础的消息类型,支持三种发送方式,适用于绝大部分异步解耦场景。

4.1.1 同步发送 ​

  发送方发送消息后,同步等待 Broker 返回结果。可靠性最高,但发送方会阻塞等待。

java
public class SyncProducer {
    public static void main(String[] args) throws Exception {
        DefaultMQProducer producer = new DefaultMQProducer("producer_group");
        producer.setNamesrvAddr("127.0.0.1:9876");
        producer.start();

        Message msg = new Message(
            "TopicTest",              // Topic
            "TagA",                   // Tag
            "Hello RocketMQ".getBytes(RemotingHelper.DEFAULT_CHARSET)
        );

        SendResult result = producer.send(msg);
        System.out.printf("发送结果: %s%n", result);

        producer.shutdown();
    }
}

4.1.2 异步发送 ​

  发送方发送消息后立即返回,通过回调处理发送结果。吞吐量高,适用于对延迟敏感的场景。

java
public class AsyncProducer {
    public static void main(String[] args) throws Exception {
        DefaultMQProducer producer = new DefaultMQProducer("producer_group");
        producer.setNamesrvAddr("127.0.0.1:9876");
        producer.start();
        producer.setRetryTimesWhenSendAsyncFailed(0);

        Message msg = new Message("TopicTest", "TagA",
            "Hello Async RocketMQ".getBytes(RemotingHelper.DEFAULT_CHARSET));

        producer.send(msg, new SendCallback() {
            @Override
            public void onSuccess(SendResult sendResult) {
                System.out.printf("发送成功: %s%n", sendResult);
            }

            @Override
            public void onException(Throwable e) {
                System.out.printf("发送失败: %s%n", e.getMessage());
            }
        });

        // 异步发送,需要等待回调执行
        Thread.sleep(3000);
        producer.shutdown();
    }
}

4.1.3 单向发送 ​

  发送方发送消息后不关心发送结果,性能最高,但不保证消息到达。适用于日志采集等可容忍丢失的场景。

java
public class OnewayProducer {
    public static void main(String[] args) throws Exception {
        DefaultMQProducer producer = new DefaultMQProducer("producer_group");
        producer.setNamesrvAddr("127.0.0.1:9876");
        producer.start();

        Message msg = new Message("TopicTest", "TagA",
            "Hello Oneway RocketMQ".getBytes(RemotingHelper.DEFAULT_CHARSET));

        producer.sendOneway(msg);  // 不关心结果,不等待,不重试

        producer.shutdown();
    }
}

三种发送方式对比:

发送方式可靠性吞吐量延迟适用场景
同步发送高中高(等待 Broker 确认)重要业务通知,如订单状态变更
异步发送中高低(立即返回)对延迟敏感、可接受少量失败
单向发送低最高最低日志采集、埋点数据

4.2 顺序消息 ​

  顺序消息保证消息按照发送顺序被消费。RocketMQ 支持两种顺序级别:

全局顺序 vs 分区顺序:

维度全局顺序分区顺序
实现方式一个 Topic 只有一个 Queue一个 Topic 有多个 Queue,同类消息进同一 Queue
并发能力低(串行消费)高(不同 Queue 可并行消费)
实现复杂度低中(需要 MessageQueueSelector)
适用场景对全局顺序有强要求的场景只需要局部有序的场景(如单个订单的操作有序)

分区顺序消息示例:

java
// ========== 生产者:按订单 ID 路由到同一 Queue ==========
public class OrderedProducer {
    public static void main(String[] args) throws Exception {
        DefaultMQProducer producer = new DefaultMQProducer("producer_group");
        producer.setNamesrvAddr("127.0.0.1:9876");
        producer.start();

        String[] tags = new String[]{"TagA", "TagB", "TagC", "TagD", "TagE"};

        for (int i = 0; i < 100; i++) {
            int orderId = i % 10;  // 10 个订单
            Message msg = new Message(
                "TopicOrdered",
                tags[i % tags.length],
                "KEY" + i,
                ("Ordered Message " + i).getBytes()
            );

            SendResult result = producer.send(msg, new MessageQueueSelector() {
                @Override
                public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
                    Integer id = (Integer) arg;
                    // 同一个 orderId 的消息总是路由到同一个 Queue
                    int index = id % mqs.size();
                    return mqs.get(index);
                }
            }, orderId);

            System.out.printf("发送结果: %s%n", result);
        }

        producer.shutdown();
    }
}
java
// ========== 消费者:顺序消费 ==========
public class OrderedConsumer {
    public static void main(String[] args) throws Exception {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("consumer_group");
        consumer.setNamesrvAddr("127.0.0.1:9876");
        consumer.subscribe("TopicOrdered", "TagA || TagB || TagC");

        // 注册顺序消费监听器
        consumer.registerMessageListener(new MessageListenerOrderly() {
            @Override
            public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs,
                                                       ConsumeOrderlyContext context) {
                // 自动上锁,保证同一 Queue 的消息在同一个线程中顺序消费
                context.setAutoCommit(true);
                for (MessageExt msg : msgs) {
                    System.out.printf("线程: %s, Queue: %s, 内容: %s%n",
                        Thread.currentThread().getName(),
                        msg.getQueueId(),
                        new String(msg.getBody()));
                }
                return ConsumeOrderlyStatus.SUCCESS;
            }
        });

        consumer.start();
        System.out.println("顺序消费者启动...");
    }
}

顺序消费的关键点:

  • MessageListenerOrderly 会为每个 ConsumerQueue 加锁,确保同一时刻只有一个线程消费该 Queue
  • 生产端使用 MessageQueueSelector 将需要顺序的消息路由到同一个 Queue
  • 顺序消费会牺牲一定的并发性能,非必要不开启

4.3 延迟消息 ​

  延迟消息是指消息发送后,消费者不会立即收到,而是等待指定的延迟时间后才会被消费。适用于超时关单、定时提醒等场景。

RocketMQ 支持的 18 个延迟级别:

| 级别 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | |------|---|---|---|---|---|---|---|---|----|----|----|----|----|----|----|----|----|----|----|----| | 延迟 | 1s | 5s | 10s | 30s | 1m | 2m | 3m | 4m | 5m | 6m | 7m | 8m | 9m | 10m | 20m | 30m | 1h | 2h |

注意:RocketMQ 不支持任意精度的延迟时间,只能从这 18 个级别中选择。如果需要更灵活的延迟,可以在业务层自行实现(如使用 Redis + 定时任务)。

延迟消息示例:

java
// ========== 生产者:发送延迟消息 ==========
public class DelayProducer {
    public static void main(String[] args) throws Exception {
        DefaultMQProducer producer = new DefaultMQProducer("producer_group");
        producer.setNamesrvAddr("127.0.0.1:9876");
        producer.start();

        for (int i = 0; i < 5; i++) {
            Message msg = new Message(
                "TopicDelay",
                "TagDelay",
                ("Delay Message " + i).getBytes()
            );

            // 设置延迟级别:level 3 = 10 秒
            msg.setDelayTimeLevel(3);

            SendResult result = producer.send(msg);
            System.out.printf("发送延迟消息: %s, 发送时间: %s%n",
                result.getMsgId(),
                System.currentTimeMillis());
        }

        producer.shutdown();
    }
}
java
// ========== 消费者:消费延迟消息 ==========
public class DelayConsumer {
    public static void main(String[] args) throws Exception {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("consumer_group");
        consumer.setNamesrvAddr("127.0.0.1:9876");
        consumer.subscribe("TopicDelay", "*");

        consumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
                                                             ConsumeConcurrentlyContext context) {
                for (MessageExt msg : msgs) {
                    System.out.printf("收到延迟消息: %s, 存储时间: %s, 接收时间: %s%n",
                        new String(msg.getBody()),
                        msg.getStoreTimestamp(),    // 消息存储时间
                        System.currentTimeMillis()); // 实际消费时间
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });

        consumer.start();
        System.out.println("延迟消息消费者启动...");
    }
}

延迟消息的实现原理:

延迟消息在 Broker 端的处理流程:

  Producer 发送消息(delayLevel=3,即 10s)
    │
    ▼
  Broker 收到消息
    │
    ├── 将消息的原始 Topic 改为 SCHEDULE_TOPIC_XXXX
    ├── 将消息存储到 CommitLog
    ├── 根据 delayLevel 投递到对应的 ConsumeQueue
    │
    ▼
  定时任务(Timer)扫描 SCHEDULE_TOPIC_XXXX
    │
    ├── 检查消息是否达到延迟时间
    │   ├── 未到达 → 继续等待
    │   └── 已到达 → 恢复原始 Topic,投递到真实 ConsumeQueue
    │
    ▼
  Consumer 消费到消息(10s 后)

4.4 事务消息 ​

  事务消息是 RocketMQ 实现分布式事务最终一致性的核心机制。它通过"半消息 + 事务状态回查"的方式,保证本地事务和消息发送的原子性。

事务消息的执行流程:

┌──────────┐                    ┌──────────┐                    ┌──────────┐
│ Producer │                    │  Broker  │                    │ Consumer │
└────┬─────┘                    └────┬─────┘                    └────┬─────┘
     │                               │                               │
     │  ① 发送半消息(Half Msg)       │                               │
     │──────────────────────────────►│                               │
     │                               │  半消息对 Consumer 不可见       │
     │  ② 返回半消息发送结果            │                               │
     │◄──────────────────────────────│                               │
     │                               │                               │
     │  ③ 执行本地事务                 │                               │
     │  ┌─────────────────────┐      │                               │
     │  │ 扣减库存 / 更新订单    │      │                               │
     │  └──────────┬──────────┘      │                               │
     │             │                  │                               │
     │  ④ 根据本地事务结果提交/回滚      │                               │
     │─────── COMMIT / ROLLBACK ─────►│                               │
     │             │                  │                               │
     │             │  COMMIT ─────────┼──► 消息对 Consumer 可见        │
     │             │                  │──────────────────────────────►│
     │             │                  │                               │
     │             │  ROLLBACK ───────┼──► 消息被删除,不会投递         │
     │             │                  │                               │
     │  ⑤ 如果 Broker 未收到提交/回滚    │                               │
     │     或者 Producer 宕机          │                               │
     │             │                  │                               │
     │             │  ⑥ 事务状态回查     │                               │
     │             │◄─────────────────│                               │
     │             │                  │                               │
     │  ⑦ 返回本地事务最终状态          │                               │
     │             │─────────────────►│                               │
     │             │                  │                               │

事务消息代码示例:

java
public class TransactionProducer {
    public static void main(String[] args) throws Exception {
        // 事务消息生产者
        TransactionMQProducer producer = new TransactionMQProducer("transaction_group");
        producer.setNamesrvAddr("127.0.0.1:9876");

        // 自定义线程池,用于执行事务回查
        ExecutorService executorService = new ThreadPoolExecutor(
            2, 5, 100, TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(2000),
            new ThreadFactory() {
                @Override
                public Thread newThread(Runnable r) {
                    Thread thread = new Thread(r);
                    thread.setName("transaction-check-thread");
                    return thread;
                }
            }
        );
        producer.setExecutorService(executorService);

        // 设置事务监听器
        producer.setTransactionListener(new TransactionListener() {

            /**
             * 执行本地事务
             * 在发送半消息成功后调用
             */
            @Override
            public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
                // 模拟执行本地事务
                String businessKey = msg.getKeys();
                System.out.println("执行本地事务, businessKey: " + businessKey);

                try {
                    // 这里是你的业务逻辑:扣减库存、更新订单状态等
                    boolean success = doBusinessLogic(businessKey);

                    if (success) {
                        // 本地事务执行成功,提交事务消息
                        System.out.println("本地事务成功,提交消息");
                        return LocalTransactionState.COMMIT_MESSAGE;
                    } else {
                        // 本地事务执行失败,回滚事务消息
                        System.out.println("本地事务失败,回滚消息");
                        return LocalTransactionState.ROLLBACK_MESSAGE;
                    }
                } catch (Exception e) {
                    // 异常情况,返回 UNKNOW,等待 Broker 回查
                    System.out.println("本地事务异常,返回 UNKNOW");
                    return LocalTransactionState.UNKNOW;
                }
            }

            /**
             * 事务状态回查
             * 当 Broker 未收到 COMMIT/ROLLBACK,或收到 UNKNOW 时调用
             * 时间间隔:默认 60s 一次,最多回查 15 次
             */
            @Override
            public LocalTransactionState checkLocalTransaction(MessageExt msg) {
                String businessKey = msg.getKeys();
                System.out.println("事务状态回查, businessKey: " + businessKey);

                // 查询本地事务的执行状态
                boolean localTxStatus = checkLocalTransactionStatus(businessKey);

                if (localTxStatus) {
                    System.out.println("回查:本地事务已提交,提交消息");
                    return LocalTransactionState.COMMIT_MESSAGE;
                } else {
                    System.out.println("回查:本地事务已回滚,回滚消息");
                    return LocalTransactionState.ROLLBACK_MESSAGE;
                }
            }
        });

        producer.start();

        // 发送事务消息
        for (int i = 0; i < 5; i++) {
            Message msg = new Message(
                "TopicTransaction",
                "TagA",
                "KEY" + i,
                ("Transaction Message " + i).getBytes()
            );

            // 发送事务消息,第二个参数是传给 executeLocalTransaction 的 arg
            TransactionSendResult result = producer.sendMessageInTransaction(msg, null);
            System.out.printf("事务消息发送结果: %s%n", result);
        }

        // 防止程序退出(等待回查)
        Thread.sleep(60000);
        producer.shutdown();
    }

    private static boolean doBusinessLogic(String businessKey) {
        // 模拟业务逻辑
        return true;
    }

    private static boolean checkLocalTransactionStatus(String businessKey) {
        // 查询数据库,判断本地事务是否已提交
        // 比如:根据 businessKey 查订单表,看订单状态
        return true;
    }
}

事务消息的三种状态:

状态含义后续动作
COMMIT_MESSAGE本地事务提交成功Broker 将半消息标记为可投递,Consumer 可以消费
ROLLBACK_MESSAGE本地事务回滚Broker 删除半消息,Consumer 不会收到
UNKNOW本地事务状态未知Broker 等待后发起事务状态回查

事务消息的适用场景:

  • 下单扣库存:订单创建成功(本地事务) + 库存扣减消息(异步通知)
  • 充值到账:支付流水写入(本地事务) + 账户余额变更消息(异步通知)
  • 积分发放:订单完成(本地事务) + 积分变更消息(异步通知)

事务消息的局限性:

  • Consumer 端无法保证幂等,需要业务层自行实现幂等
  • 不保证强一致性,只保证最终一致性
  • 事务回查有时间窗口,极端情况下可能延迟数分钟

五、消费模式 ​

5.1 集群消费(Clustering) ​

  默认模式。一条消息只会被 ConsumerGroup 中的一个消费者消费。消费者组内的消费者分摊 Queue,实现负载均衡和水平扩展。

集群消费模式:

  Topic: Order
  Queue: Q0, Q1, Q2, Q3, Q4, Q5
  ConsumerGroup: order-consumer-group

  ┌──────────┐  ┌──────────┐  ┌──────────┐
  │   C-1    │  │   C-2    │  │   C-3    │
  │          │  │          │  │          │
  │  Q0, Q1  │  │  Q2, Q3  │  │  Q4, Q5  │
  └──────────┘  └──────────┘  └──────────┘

  消息 M1 发送到 Q0 → 只有 C-1 消费
  消息 M2 发送到 Q3 → 只有 C-2 消费
  消息 M3 发送到 Q5 → 只有 C-3 消费

  扩容:新增 C-4,自动重平衡,C-4 从各消费者分走部分 Queue
  缩容:C-3 宕机,C-1 和 C-2 自动接管 Q4, Q5
java
// 集群消费(默认模式)
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("order-consumer-group");
consumer.setNamesrvAddr("127.0.0.1:9876");
consumer.setMessageModel(MessageModel.CLUSTERING);  // 默认就是集群模式
consumer.subscribe("Order", "*");

consumer.registerMessageListener(new MessageListenerConcurrently() {
    @Override
    public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
                                                     ConsumeConcurrentlyContext context) {
        for (MessageExt msg : msgs) {
            System.out.printf("集群消费: %s%n", new String(msg.getBody()));
        }
        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
    }
});

consumer.start();

5.2 广播消费(Broadcasting) ​

  一条消息会被 ConsumerGroup 中的每一个消费者都消费一次。每个消费者都消费全量消息。

广播消费模式:

  Topic: Order
  ConsumerGroup: order-broadcast-group

  ┌──────────┐  ┌──────────┐  ┌──────────┐
  │   C-1    │  │   C-2    │  │   C-3    │
  │          │  │          │  │          │
  │  全量消息  │  │  全量消息  │  │  全量消息  │
  └──────────┘  └──────────┘  └──────────┘

  消息 M1 → C-1、C-2、C-3 都收到
  消息 M2 → C-1、C-2、C-3 都收到
  消息 M3 → C-1、C-2、C-3 都收到

  ⚠ 注意:广播消费不支持顺序消息
  ⚠ 注意:广播消费的消费进度存储在 Consumer 本地,重启后从上次进度继续
java
// 广播消费
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("order-broadcast-group");
consumer.setNamesrvAddr("127.0.0.1:9876");
consumer.setMessageModel(MessageModel.BROADCASTING);  // 广播模式
consumer.subscribe("Order", "*");

consumer.registerMessageListener(new MessageListenerConcurrently() {
    @Override
    public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
                                                     ConsumeConcurrentlyContext context) {
        for (MessageExt msg : msgs) {
            System.out.printf("广播消费: %s%n", new String(msg.getBody()));
        }
        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
    }
});

consumer.start();

5.3 两种模式对比 ​

维度集群消费(Clustering)广播消费(Broadcasting)
消息投递一条消息只投递给组内一个消费者一条消息投递给组内所有消费者
负载均衡支持,Queue 在组内分摊不支持,每个消费者消费全量
消费进度存储在 Broker 端存储在 Consumer 本地
水平扩展可以,新增消费者自动分担新增消费者独立消费全量
顺序消息支持不支持
重试消息支持不支持
适用场景订单处理、库存扣减(一条消息处理一次)配置刷新、缓存刷新(每个实例都需处理)

六、消息可靠性 ​

  消息可靠性是 RocketMQ 的核心竞争力。RocketMQ 通过刷盘机制和主从复制两个维度来保证消息的可靠性,但可靠性和性能总是存在权衡。

6.1 刷盘机制 ​

  刷盘是指将消息从内存(Page Cache)写入磁盘的过程。

同步刷盘:

Producer 发送消息
  │
  ▼
Broker 接收消息
  │
  ├── 写入 Page Cache(内存)
  ├── 立即调用 fsync() 强制刷盘
  │
  ▼
刷盘完成后返回 ACK 给 Producer
  │
  ▼
Producer 收到 ACK,确认消息已持久化

  可靠性:★★★★★  消息不丢
  性能:   ★★☆☆☆  刷盘耗时约 1-10ms,影响吞吐量

异步刷盘:

Producer 发送消息
  │
  ▼
Broker 接收消息
  │
  ├── 写入 Page Cache(内存)
  ├── 立即返回 ACK 给 Producer
  │
  ▼
后台线程定期(默认 500ms)将 Page Cache 刷入磁盘

  可靠性:★★★☆☆  宕机可能丢失最近 500ms 的消息
  性能:   ★★★★★  吞吐量极高

6.2 主从复制 ​

同步复制(SYNC_MASTER):

Producer 发送消息
  │
  ▼
Master Broker 接收消息
  │
  ├── 写入 Master CommitLog
  ├── 等待 Slave 同步完成
  │
  ▼
Master 和 Slave 都写入成功后
  │
  ▼
返回 ACK 给 Producer

  可靠性:★★★★★  Master 宕机,Slave 有完整数据
  性能:   ★★★☆☆  增加主从同步延迟

异步复制(ASYNC_MASTER):

Producer 发送消息
  │
  ▼
Master Broker 接收消息
  │
  ├── 写入 Master CommitLog
  ├── 立即返回 ACK 给 Producer
  │
  ▼
后台异步同步给 Slave

  可靠性:★★★☆☆  Master 宕机,Slave 可能缺少最新消息
  性能:   ★★★★★  无主从同步延迟

6.3 四种组合对比 ​

组合刷盘复制可靠性性能适用场景
同步刷盘 + 同步复制SYNC_FLUSHSYNC_MASTER最高最低金融支付、对账
同步刷盘 + 异步复制SYNC_FLUSHASYNC_MASTER高中重要业务,可接受主从切换丢失少量
异步刷盘 + 同步复制ASYNC_FLUSHSYNC_MASTER中高中可靠性要求较高,但需要高吞吐
异步刷盘 + 异步复制ASYNC_FLUSHASYNC_MASTER一般最高日志采集、埋点数据(可容忍丢失)

6.4 生产者重试机制 ​

  当 Producer 发送消息失败时,RocketMQ 会自动重试,保证消息不丢失。

java
// 生产者重试配置
DefaultMQProducer producer = new DefaultMQProducer("producer_group");

// 同步发送失败重试次数(默认 2 次)
producer.setRetryTimesWhenSendFailed(3);

// 异步发送失败重试次数(默认 2 次)
producer.setRetryTimesWhenSendAsyncFailed(3);

// 重试时尝试发送到其他 Broker(默认 false)
producer.setRetryAnotherBrokerWhenNotStoreOK(true);

// 发送超时时间(默认 3000ms)
producer.setSendMsgTimeout(5000);

生产者重试流程:

发送消息失败
  │
  ├── 第 1 次重试(立即)
  │   ├── 成功 → 返回成功
  │   └── 失败 → 继续
  │
  ├── 第 2 次重试(立即)
  │   ├── 成功 → 返回成功
  │   └── 失败 → 继续
  │
  ├── 第 3 次重试(立即)
  │   ├── 成功 → 返回成功
  │   └── 失败 → 抛出异常,业务层处理(记录日志、告警、人工介入)
  │
  └── setRetryAnotherBrokerWhenNotStoreOK(true)
      重试时会避开上一轮失败的 Broker,选择其他 Broker

6.5 消费者重试机制 ​

  当消费者消费消息失败时,RocketMQ 会将消息发送到重试队列,延迟后重新投递。

重试队列:

消费失败
  │
  ▼
消息投递到重试队列:%RETRY%{ConsumerGroup}
  │
  ├── 第 1 次重试:10s 后
  ├── 第 2 次重试:30s 后
  ├── 第 3 次重试:1m 后
  ├── 第 4 次重试:2m 后
  ├── ...
  ├── 第 16 次重试:2h 后
  │
  ▼
仍失败 → 消息进入死信队列:%DLQ%{ConsumerGroup}

重试间隔时间表:

| 重试次数 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | |----------|---|---|---|---|---|---|---|---|---|---|----|----|----|----|----|----|----|----| | 延迟时间 | 10s | 30s | 1m | 2m | 3m | 4m | 5m | 6m | 7m | 8m | 9m | 10m | 20m | 30m | 1h | 2h |

死信队列(DLQ - Dead Letter Queue):

当消息重试 16 次仍然失败后,消息会被移入死信队列。死信队列中的消息需要人工处理或通过定时任务处理。

java
// 消费者重试配置
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("consumer_group");

// 最大重试次数(默认 16 次),超过后进入死信队列
consumer.setMaxReconsumeTimes(16);

consumer.registerMessageListener(new MessageListenerConcurrently() {
    @Override
    public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
                                                     ConsumeConcurrentlyContext context) {
        for (MessageExt msg : msgs) {
            try {
                // 业务处理
                processMessage(msg);
            } catch (Exception e) {
                // 获取当前重试次数
                int reconsumeTimes = msg.getReconsumeTimes();
                System.out.printf("消息重试第 %d 次%n", reconsumeTimes);

                if (reconsumeTimes >= 3) {
                    // 重试超过 3 次,记录到 DB 或发送告警,手动处理
                    saveToDeadLetterTable(msg);
                    return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
                }

                // 返回 RECONSUME_LATER,消息会进入重试队列
                return ConsumeConcurrentlyStatus.RECONSUME_LATER;
            }
        }
        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
    }
});

七、核心配置 ​

7.1 Spring Cloud Stream RocketMQ YAML 配置 ​

yaml
spring:
  application:
    name: order-service

  cloud:
    stream:
      # 默认绑定器
      default-binder: rocketmq

      # 绑定器配置
      rocketmq:
        binder:
          # NameServer 地址(必填)
          name-server: 127.0.0.1:9876
          # 可选:多个 NameServer 用分号分隔
          # name-server: 127.0.0.1:9876;127.0.0.2:9876

          # 生产者配置
          producers:
            # 生产者组名
            group: order-producer-group
            # 发送消息超时时间(毫秒)
            send-message-timeout: 3000
            # 同步发送失败重试次数
            retry-times-when-send-failed: 3
            # 异步发送失败重试次数
            retry-times-when-send-async-failed: 3
            # 消息体最大字节数
            max-message-size: 4194304
            # 压缩消息体阈值(大于此值自动压缩)
            compress-message-body-threshold: 4096
            # 失败时尝试发送到其他 Broker
            retry-next-server: true
            # VIP 通道(云环境可能需关闭)
            vip-channel-enabled: false

          # 消费者配置
          consumers:
            # 消费者组名
            group: order-consumer-group
            # 消息模型:CLUSTERING(集群)/ BROADCASTING(广播)
            message-model: CLUSTERING
            # 消费线程池最小线程数
            consume-thread-min: 20
            # 消费线程池最大线程数
            consume-thread-max: 64
            # 每次拉取消息数量
            pull-batch-size: 32
            # 批量消费时每次最大消息数
            max-batch-size: 1
            # 消费超时时间(分钟)
            consume-timeout: 15
            # 最大重试次数
            max-reconsume-times: 3
            # 消费失败后挂起当前队列的时间(毫秒)
            suspend-current-queue-time-millis: 1000
            # 消息过滤表达式类型:tag / sql92
            selector-type: tag
            # 消息过滤表达式
            selector-expression: "*"

      # 绑定定义
      bindings:
        # 输出通道(生产者)
        order-output:
          # 目标 Topic
          destination: order-topic
          # 内容类型
          content-type: application/json
          # 生产者配置
          producer:
            # 生产者组(可覆盖全局配置)
            group: order-producer-group
            # 事务消息
            transactional: false
            # 消息标签(用于消费者过滤)
            # 也可以在这里指定 tag
            # tags: order-create

        # 输入通道(消费者)
        order-input:
          destination: order-topic
          content-type: application/json
          # 消费者组
          group: order-consumer-group
          # 消费者配置
          consumer:
            # 消息过滤:只消费 tag 为 order-create 或 order-pay 的消息
            tags: order-create || order-pay
            # 并发消费
            concurrency: 5
            # 最大重试次数
            max-attempts: 3

    # 函数式编程风格(Spring Cloud Stream 3.x+ 推荐)
    function:
      definition: orderConsumer;orderProducer

    stream:
      bindings:
        orderConsumer-in-0:
          destination: order-topic
          group: order-consumer-group
        orderProducer-out-0:
          destination: order-topic

7.2 原生 RocketMQ 配置(rocketmq-client 方式) ​

yaml
rocketmq:
  # NameServer 地址
  name-server: 127.0.0.1:9876

  # 生产者配置
  producer:
    # 生产者组
    group: order-producer-group
    # 发送消息超时时间
    send-message-timeout: 3000
    # 重试次数
    retry-times-when-send-failed: 3
    # 异步发送重试次数
    retry-times-when-send-async-failed: 3
    # 消息最大大小
    max-message-size: 4194304
    # 压缩阈值
    compress-message-body-threshold: 4096
    # 失败时切换 Broker
    retry-next-server: true
    # 访问密钥(ACL 模式下需要)
    access-key: your-access-key
    # 访问密钥(ACL 模式下需要)
    secret-key: your-secret-key

  # 消费者配置
  consumer:
    # 消费者组
    group: order-consumer-group
    # 消息模型
    message-model: CLUSTERING
    # 消费线程数
    consume-thread-min: 20
    consume-thread-max: 64
    # 批量消费最大消息数
    consume-message-batch-max-size: 1
    # 从哪个位置开始消费
    # CONSUME_FROM_LAST_OFFSET: 从队列最后开始(默认)
    # CONSUME_FROM_FIRST_OFFSET: 从队列开头开始
    # CONSUME_FROM_TIMESTAMP: 从指定时间开始
    consume-from-where: CONSUME_FROM_LAST_OFFSET
    # 消息过滤类型
    selector-type: tag
    # 消息过滤表达式
    selector-expression: "*"
    # 最大重试次数
    max-reconsume-times: 3

  # 消费者监听器配置(多个消费者组)
  listeners:
    # 消费者组 1
    - group: order-consumer-group
      topic: order-topic
      selector-type: tag
      selector-expression: order-create || order-pay
      message-model: CLUSTERING
      consume-thread-max: 64
    # 消费者组 2
    - group: log-consumer-group
      topic: log-topic
      selector-type: tag
      selector-expression: "*"
      message-model: CLUSTERING
      consume-thread-max: 20

八、Spring Cloud Stream + RocketMQ 集成 ​

8.1 依赖配置 ​

xml
<!-- pom.xml -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-stream-rocketmq</artifactId>
    <version>2021.0.5.0</version>
</dependency>

8.2 生产者(函数式编程风格) ​

java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.stream.function.StreamBridge;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;

@Service
public class OrderProducer {

    @Autowired
    private StreamBridge streamBridge;

    /**
     * 发送普通消息
     */
    public void sendOrderMessage(Order order) {
        Message<Order> message = MessageBuilder
            .withPayload(order)
            .setHeader("tag", "order-create")        // 设置 Tag
            .setHeader("keys", order.getOrderId())   // 设置 Key(用于消息查询)
            .build();

        boolean result = streamBridge.send("orderProducer-out-0", message);
        if (result) {
            System.out.println("消息发送成功");
        } else {
            System.out.println("消息发送失败");
        }
    }

    /**
     * 发送延迟消息
     */
    public void sendDelayMessage(Order order) {
        Message<Order> message = MessageBuilder
            .withPayload(order)
            .setHeader("tag", "order-delay")
            .setHeader("delayLevel", 3)   // 延迟级别 3 = 10 秒
            .build();

        streamBridge.send("orderProducer-out-0", message);
    }

    /**
     * 发送事务消息(需要配合 TransactionListener)
     */
    public void sendTransactionMessage(Order order) {
        Message<Order> message = MessageBuilder
            .withPayload(order)
            .setHeader("tag", "order-transaction")
            .setHeader("transactional", true)         // 标记为事务消息
            .setHeader("transactionId", order.getOrderId())
            .build();

        streamBridge.send("orderProducer-out-0", message);
    }
}

8.3 消费者(函数式编程风格) ​

java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.Message;
import org.springframework.messaging.handler.annotation.Header;

import java.util.function.Consumer;

@Configuration
public class OrderConsumer {

    /**
     * 普通消息消费者
     * 函数名 = binding name 中 -in- 前面的部分
     * 即 orderConsumer-in-0 → 函数名为 orderConsumer
     */
    @Bean
    public Consumer<Message<Order>> orderConsumer() {
        return message -> {
            Order order = message.getPayload();
            String tag = (String) message.getHeaders().get("tag");
            String keys = (String) message.getHeaders().get("keys");

            System.out.printf("收到消息: tag=%s, keys=%s, order=%s%n",
                tag, keys, order);

            // 业务处理
            processOrder(order);
        };
    }

    /**
     * 带条件过滤的消费者
     * 只消费 tag 为 order-pay 的消息
     *
     * 在 application.yml 中配置:
     * spring.cloud.stream.bindings.orderPayConsumer-in-0.consumer.tags: order-pay
     */
    @Bean
    public Consumer<Message<Order>> orderPayConsumer() {
        return message -> {
            Order order = message.getPayload();
            System.out.println("收到支付消息: " + order.getOrderId());
            processPayment(order);
        };
    }

    private void processOrder(Order order) {
        // 订单业务处理
    }

    private void processPayment(Order order) {
        // 支付业务处理
    }
}

8.4 消息过滤配置 ​

yaml
spring:
  cloud:
    stream:
      bindings:
        # 消费者 1:只消费订单创建消息
        orderCreateConsumer-in-0:
          destination: order-topic
          group: order-create-consumer
          consumer:
            tags: order-create              # 只消费 tag=order-create 的消息

        # 消费者 2:只消费订单支付消息
        orderPayConsumer-in-0:
          destination: order-topic
          group: order-pay-consumer
          consumer:
            tags: order-pay                 # 只消费 tag=order-pay 的消息

        # 消费者 3:消费订单创建和支付消息
        orderAllConsumer-in-0:
          destination: order-topic
          group: order-all-consumer
          consumer:
            tags: order-create || order-pay # 消费两种 tag 的消息

        # 消费者 4:消费所有消息(不区分 tag)
        orderLogConsumer-in-0:
          destination: order-topic
          group: order-log-consumer
          consumer:
            tags: "*"                       # 消费所有消息

8.5 事务消息完整示例 ​

java
import org.apache.rocketmq.spring.annotation.RocketMQTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionState;
import org.apache.rocketmq.spring.support.RocketMQHeaders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.stream.function.StreamBridge;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;

@Service
public class TransactionOrderService {

    @Autowired
    private StreamBridge streamBridge;

    @Autowired
    private OrderService orderService;

    /**
     * 发送事务消息
     */
    public void sendOrderTransaction(Order order) {
        Message<Order> message = MessageBuilder
            .withPayload(order)
            .setHeader(RocketMQHeaders.TRANSACTION_ID, order.getOrderId())
            .build();

        streamBridge.send("orderTransaction-out-0", message);
    }
}

/**
 * 事务消息监听器
 */
@RocketMQTransactionListener
class OrderTransactionListener implements RocketMQLocalTransactionListener {

    @Autowired
    private OrderService orderService;

    /**
     * 执行本地事务
     */
    @Override
    public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) {
        String transactionId = (String) msg.getHeaders()
            .get(RocketMQHeaders.TRANSACTION_ID);

        try {
            // 执行本地事务:创建订单
            Object payload = msg.getPayload();
            boolean result = orderService.createOrder((Order) payload);

            if (result) {
                return RocketMQLocalTransactionState.COMMIT;
            } else {
                return RocketMQLocalTransactionState.ROLLBACK;
            }
        } catch (Exception e) {
            return RocketMQLocalTransactionState.UNKNOWN;
        }
    }

    /**
     * 事务状态回查
     */
    @Override
    public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
        String transactionId = (String) msg.getHeaders()
            .get(RocketMQHeaders.TRANSACTION_ID);

        // 根据 transactionId 查询本地事务状态
        Order order = orderService.getOrderByTransactionId(transactionId);
        if (order != null && order.getStatus() == OrderStatus.CREATED) {
            return RocketMQLocalTransactionState.COMMIT;
        } else {
            return RocketMQLocalTransactionState.ROLLBACK;
        }
    }
}
yaml
# application.yml - 事务消息绑定配置
spring:
  cloud:
    stream:
      bindings:
        orderTransaction-out-0:
          destination: order-topic
          producer:
            transactional: true   # 开启事务消息
            group: order-transaction-producer

8.6 完整配置汇总 ​

yaml
spring:
  application:
    name: order-service

  cloud:
    stream:
      function:
        definition: orderConsumer;orderPayConsumer;orderTransaction

      rocketmq:
        binder:
          name-server: 127.0.0.1:9876
          producers:
            group: order-producer-group
            send-message-timeout: 3000
            retry-times-when-send-failed: 3
          consumers:
            group: order-consumer-group
            consume-thread-max: 64
            max-reconsume-times: 3
            message-model: CLUSTERING

      bindings:
        # 生产者
        orderProducer-out-0:
          destination: order-topic
          content-type: application/json

        # 事务消息生产者
        orderTransaction-out-0:
          destination: order-topic
          content-type: application/json
          producer:
            transactional: true

        # 消费者:消费创建消息
        orderConsumer-in-0:
          destination: order-topic
          content-type: application/json
          group: order-create-consumer
          consumer:
            tags: order-create
            concurrency: 5

        # 消费者:消费支付消息
        orderPayConsumer-in-0:
          destination: order-topic
          content-type: application/json
          group: order-pay-consumer
          consumer:
            tags: order-pay
            concurrency: 3

九、RocketMQ vs Kafka vs RabbitMQ ​

9.1 综合对比 ​

维度RocketMQKafkaRabbitMQ
开发语言JavaJava/ScalaErlang
所属Apache / 阿里巴巴Apache / LinkedInVMware / Pivotal
吞吐量极高(十万级/秒)极高(百万级/秒)中等(万级/秒)
延迟毫秒级毫秒级微秒级
可靠性高(同步刷盘/同步复制)高(ISR 副本机制)高(持久化+镜像队列)
消息堆积极强(亿级堆积,性能衰减极小)强(依赖磁盘容量)弱(堆积严重影响性能)
事务消息原生支持(半消息+回查)支持(Kafka Transactions)不支持原生事务消息
顺序消息原生支持(分区顺序 + 全局顺序)支持(分区内有序)支持(队列内有序)
延迟消息原生支持(18 个延迟级别)不支持(需自行实现)支持(插件或死信+TTL)
消息过滤Tag + SQL92 表达式不支持服务端过滤(需客户端过滤)支持 Routing Key
消息重试原生支持(重试队列+死信队列)不支持(需自行实现)支持(死信队列)
消费模式集群 / 广播集群(ConsumerGroup)推模式 / 拉模式
消息回溯支持(按时间回溯)支持(按 Offset 回溯)不支持
定时消息支持(18 个延迟级别)不支持不支持
适用场景业务消息、事务消息、电商场景日志采集、流计算、大数据企业应用集成、复杂路由
社区活跃度活跃(阿里+Apache)非常活跃(Confluent)活跃
运维复杂度中等较高(依赖 Zookeeper)较低
协议自定义(Remoting)自定义(二进制)AMQP、MQTT、STOMP

9.2 选型建议 ​

                    开始
                     │
                     ▼
         ┌─ 是否大数据/日志采集场景? ─┐
         │ 是                          │ 否
         ▼                             ▼
       Kafka                 ┌─ 是否需要事务消息? ─┐
       (高吞吐、流计算)       │ 是                    │ 否
                              ▼                       ▼
                           RocketMQ           ┌─ 是否需要复杂路由? ─┐
                       (电商、金融、分布式事务)  │ 是                  │ 否
                                               ▼                     ▼
                                           RabbitMQ             RocketMQ
                                      (企业集成、复杂路由)     (通用业务消息)

一句话总结:

场景推荐原因
电商业务(订单、支付、库存)RocketMQ事务消息、顺序消息、延迟消息全支持
日志采集、大数据管道Kafka吞吐量最高,生态集成好
企业应用集成、复杂路由RabbitMQAMQP 标准协议,路由灵活,运维简单
金融级可靠性RocketMQ同步刷盘 + 同步复制,消息零丢失
微服务异步通信RocketMQSpring Cloud Stream 原生集成,开发体验好

十、面试要点 ​

10.1 RocketMQ 如何保证消息不丢失? ​

  消息不丢失需要从生产端、Broker 端、消费端三个阶段分别保证。

生产端                     Broker 端                  消费端
   │                          │                         │
   ▼                          ▼                         ▼
┌─────────┐              ┌───────────┐             ┌──────────┐
│ 同步发送  │              │ 同步刷盘    │             │ 手动提交   │
│ 重试机制  │              │ 主从同步    │             │ 消费重试   │
│ 失败告警  │              │ 多副本部署  │             │ 幂等处理   │
└─────────┘              └───────────┘             └──────────┘

生产端:

  • 使用同步发送,等待 Broker 返回确认
  • 配置重试次数(retryTimesWhenSendFailed)
  • 发送失败记录日志 + 告警,必要时人工介入

Broker 端:

  • 同步刷盘(flushDiskType = SYNC_FLUSH),消息写入磁盘后才返回 ACK
  • 同步复制(brokerRole = SYNC_MASTER),Master 和 Slave 都写入后才返回 ACK
  • 多 Broker 多副本部署,避免单点故障

消费端:

  • 消费成功后再返回 CONSUME_SUCCESS,不要提前 ACK
  • 消费失败返回 RECONSUME_LATER,利用重试队列重试
  • 业务层实现幂等消费,防止重复消费导致的业务异常

10.2 事务消息的原理是什么? ​

  RocketMQ 的事务消息基于"两阶段提交 + 事务状态回查"实现:

阶段一:发送半消息
  Producer 发送一条"半消息"到 Broker
  半消息对 Consumer 不可见(不会投递)
  
阶段二:执行本地事务 + 提交/回滚
  Producer 收到半消息发送成功的响应后,执行本地事务
  本地事务成功 → 向 Broker 发送 COMMIT
  本地事务失败 → 向 Broker 发送 ROLLBACK
  
异常处理:事务状态回查
  如果 Broker 长时间未收到 COMMIT/ROLLBACK(Producer 宕机、网络中断等)
  Broker 会主动向 Producer 发起事务状态回查
  Producer 查询本地事务的最终状态,返回 COMMIT 或 ROLLBACK
  Broker 默认回查 15 次,每次间隔 60s

核心要点:

  • 半消息是存在 CommitLog 中的,只是对 Consumer 不可见
  • 事务回查需要 Producer 端实现 checkLocalTransaction 方法
  • 不保证强一致性,只保证最终一致性

10.3 NameServer 的工作原理是什么? ​

  • NameServer 是无状态的轻量级路由注册中心,各节点之间互不通信,完全对等
  • Broker 启动时向所有 NameServer 注册路由信息(Topic、Queue、Broker 地址),之后每 30s 发送心跳
  • Producer 和 Consumer 启动时从任意 NameServer 拉取路由信息,之后每 30s 定时更新
  • 如果 NameServer 超过 120s 未收到 Broker 心跳,则将该 Broker 从路由表中移除
  • Producer 和 Consumer 获取路由信息后,直接与 Broker 通信,不经过 NameServer
  • NameServer 之间互不通信,因此可能出现短暂的路由不一致(AP 模型),但最终会一致

10.4 顺序消息是如何实现的? ​

  RocketMQ 的顺序消息实现分为生产端和消费端两部分:

生产端(MessageQueueSelector):

  • 使用 MessageQueueSelector 将需要顺序的消息路由到同一个 Queue
  • 比如按订单 ID 取模:orderId % queueSize,确保同一订单的消息进入同一 Queue
  • 同一个 Queue 中的消息是严格有序的(FIFO)

消费端(MessageListenerOrderly):

  • 使用 MessageListenerOrderly 而非 MessageListenerConcurrently
  • 为每个 ConsumerQueue 加锁,确保同一时刻只有一个线程消费该 Queue
  • 消费完成后再释放锁,然后拉取下一条消息

局限性:

  • 顺序消费会牺牲并发能力,同一个 Queue 的消息是串行消费的
  • 如果某个 Queue 的消息处理失败导致阻塞,该 Queue 后续消息都无法消费
  • 全局顺序需要将 Topic 的 Queue 数量设置为 1

10.5 延迟消息的原理是什么? ​

  • RocketMQ 不支持任意精度的延迟,只支持 18 个预定义的延迟级别
  • 延迟消息发送后,Broker 将其原始 Topic 替换为内部的 SCHEDULE_TOPIC_XXXX
  • 消息被存储到 CommitLog,同时根据 delayLevel 投递到对应的 ConsumeQueue
  • 后台定时任务(Timer)不断扫描 SCHEDULE_TOPIC_XXXX
  • 当消息到达延迟时间后,恢复原始 Topic,将消息投递到真实的 ConsumeQueue
  • Consumer 此时才能消费到该消息

18 个延迟级别: 1s / 5s / 10s / 30s / 1m / 2m / 3m / 4m / 5m / 6m / 7m / 8m / 9m / 10m / 20m / 30m / 1h / 2h

10.6 RocketMQ vs Kafka 如何选择? ​

对比维度RocketMQKafka
设计目标业务消息处理海量日志/流数据处理
消息堆积亿级堆积,性能衰减极小堆积越多性能越差
消息过滤服务端 Tag 和 SQL92 过滤不支持服务端过滤
事务消息原生支持支持但实现复杂
延迟消息原生 18 个级别不支持
消息重试原生重试队列 + 死信队列不支持
依赖自身 NameServer(轻量)Zookeeper(较重)
适用场景电商、金融、微服务大数据、日志、流计算

选择建议: 业务场景选 RocketMQ,大数据场景选 Kafka。不要为了技术而技术,选择最适合业务的技术。


附录:常用命令速查 ​

启动 RocketMQ ​

bash
# 启动 NameServer
nohup sh bin/mqnamesrv &

# 启动 Broker
nohup sh bin/mqbroker -n 127.0.0.1:9876 &

# 检查是否启动成功
jps
# 应该看到 NamesrvStartup 和 BrokerStartup

管理命令 ​

bash
# 查看所有 Topic
sh bin/mqadmin topicList -n 127.0.0.1:9876

# 创建 Topic
sh bin/mqadmin updateTopic -n 127.0.0.1:9876 -b 127.0.0.1:10911 -t order-topic

# 查看 Topic 详情
sh bin/mqadmin topicStatus -n 127.0.0.1:9876 -t order-topic

# 查看 ConsumerGroup 消费进度
sh bin/mqadmin consumerProgress -n 127.0.0.1:9876 -g order-consumer-group

# 查看集群状态
sh bin/mqadmin clusterList -n 127.0.0.1:9876

# 根据 Key 查询消息
sh bin/mqadmin queryMsgByKey -n 127.0.0.1:9876 -t order-topic -k ORDER123

参考文档: