RocketMQ实战

一、RocketMQ 概述

1.1 什么是 RocketMQ

RocketMQ 是阿里巴巴开源的分布式消息中间件,由 Java 语言开发,2016 年捐献给 Apache 基金会,成为 Apache 顶级项目。它是一款低延迟、高可靠、可伸缩、易于使用的消息中间件。

1.2 核心特性

特性 说明
高吞吐量 单机支持万级 TPS,集群可达百万级 TPS
低延迟 毫秒级消息投递延迟
高可用 支持主从同步、异步复制,99.95% 可用性
海量消息堆积 支持亿级消息堆积,堆积对性能影响小
顺序消息 支持严格的消息顺序
事务消息 支持分布式事务消息
定时消息 支持延迟消息投递
消息过滤 支持 Tag、SQL92 表达式过滤
消息轨迹 支持消息轨迹追踪
失败重试 消费失败自动重试机制

1.3 应用场景

1.3.1 异步解耦
复制代码
用户注册 → MQ → [发送邮件、发送短信、创建用户空间、赠送积分]

通过消息队列解耦系统模块,提高系统可扩展性。

1.3.2 流量削峰
复制代码
秒杀请求 → MQ缓冲 → 后端系统按处理能力消费

应对突发流量,保护后端系统。

1.3.3 数据分发
复制代码
订单系统 → MQ → [库存系统、物流系统、积分系统、数据分析系统]

一份数据,多个系统消费。

1.3.4 最终一致性
复制代码
订单系统 → 事务消息 → 库存系统

通过事务消息保证分布式事务最终一致性。

1.3.5 大数据处理
复制代码
日志采集 → MQ → [实时计算、离线分析、存储]

作为数据管道,连接数据生产者和消费者。


二、核心概念

2.1 核心组件

2.1.1 NameServer(名字服务)

作用:路由注册中心,类似 Dubbo 的 ZooKeeper,但更轻量级。

职责

  • 管理 Broker 的路由信息
  • 接收 Broker 的注册请求
  • 提供路由信息给 Producer 和 Consumer
  • 不负责消息存储和转发

特点

  • 无状态节点,节点之间无通信
  • 多个 NameServer 之间互不通信
  • Broker 向所有 NameServer 注册
  • Producer/Consumer 可连接任意 NameServer

工作机制

复制代码
Broker 每 30s 向所有 NameServer 发送心跳
NameServer 每 10s 检查 Broker 存活状态
超过 120s 未收到心跳,判定 Broker 下线
2.1.2 Broker(消息服务器)

作用:消息存储中心,负责消息的接收、存储、投递。

职责

  • 接收 Producer 发送的消息
  • 持久化消息到磁盘
  • 处理 Consumer 的消费请求
  • 消息查询功能
  • 高可用保证(主从同步)

组成部分

复制代码
Broker = Remoting Module + Client Manager + Store Service + HA Service + Index Service

核心模块

  • Remoting Module:处理客户端请求的网络模块
  • Client Manager:管理客户端(Producer/Consumer)连接
  • Store Service:消息存储模块
  • HA Service:主从同步模块
  • Index Service:消息索引模块
2.1.3 Producer(生产者)

作用:消息生产者,发送消息到 Broker。

发送方式

  1. 同步发送:等待 Broker 响应,可靠性高
  2. 异步发送:不等待响应,高吞吐量
  3. 单向发送:只管发送,不关心结果,性能最高

负载均衡

  • 轮询选择 MessageQueue
  • 支持自定义队列选择策略
2.1.4 Consumer(消费者)

作用:消息消费者,从 Broker 拉取消息进行消费。

消费模式

  1. 集群模式(Clustering):消费者组内负载均衡消费
  2. 广播模式(Broadcasting):每个消费者都消费所有消息

拉取方式

  • Pull 模式:消费者主动拉取(RocketMQ 4.x 推荐)
  • Push 模式:封装的 Pull,实现了长轮询

2.2 领域模型

2.2.1 Message(消息)

消息是数据传输的载体。

消息结构

java 复制代码
public class Message {
    private String topic;              // 主题
    private int flag;                  // 标志位
    private Map<String, String> properties;  // 扩展属性
    private byte[] body;               // 消息体
    private String transactionId;      // 事务ID
}

消息属性

  • Keys:消息索引键,用于快速查找消息
  • Tags:消息标签,用于消息过滤
  • DelayTimeLevel:延迟级别
  • WaitStoreMsgOK:是否等待存储完成
2.2.2 Topic(主题)

定义:消息的第一级分类,逻辑概念。

特点

  • 一个 Topic 可以分布在多个 Broker 上
  • 一个 Topic 包含多个 MessageQueue
  • 支持通配符订阅(仅在某些场景)
2.2.3 Tag(标签)

定义:消息的第二级分类,用于细分 Topic。

使用场景

复制代码
Topic: OrderTopic
Tags: CreateOrder, PayOrder, CancelOrder, RefundOrder

过滤机制

  • Broker 端过滤:只传输匹配的消息,节省网络带宽
  • Consumer 端二次过滤:SQL92 表达式过滤
2.2.4 MessageQueue(消息队列)

定义:Topic 的物理分区,消息存储和负载均衡的最小单位。

特性

  • 一个 Topic 包含多个 MessageQueue
  • 默认一个 Broker 创建 4 个读队列 + 4 个写队列
  • MessageQueue 是有序的

标识

复制代码
MessageQueue = {Topic, BrokerName, QueueId}
2.2.5 ConsumerGroup(消费者组)

定义:相同角色的消费者集合。

特点

  • 集群模式下,一条消息只会被组内一个消费者消费
  • 广播模式下,一条消息会被组内所有消费者消费
  • 不同 ConsumerGroup 之间相互独立

三、架构设计

3.1 整体架构

复制代码
┌─────────────────────────────────────────────────────────────┐
│                         NameServer Cluster                   │
│  ┌──────────┐    ┌──────────┐    ┌──────────┐              │
│  │NameServer│    │NameServer│    │NameServer│              │
│  └─────┬────┘    └─────┬────┘    └─────┬────┘              │
└────────┼───────────────┼───────────────┼─────────────────────┘
         │               │               │
         │ 注册/心跳     │               │ 获取路由
         │               │               │
    ┌────▼───────────────▼───────────────▼────┐
    │           Broker Cluster                 │
    │  ┌────────────┐      ┌────────────┐     │
    │  │  Broker-M  │◄────►│  Broker-S  │     │
    │  │  (Master)  │ 同步  │  (Slave)   │     │
    │  └──┬─────┬───┘      └──┬─────┬───┘     │
    │     │     │             │     │          │
    └─────┼─────┼─────────────┼─────┼──────────┘
          │     │             │     │
      发送│     │投递     发送│     │投递
          │     │             │     │
    ┌─────▼─────┴─────┐ ┌─────▼─────┴─────┐
    │    Producer      │ │    Consumer      │
    │  ┌───────────┐   │ │  ┌───────────┐  │
    │  │Producer-1 │   │ │  │Consumer-1 │  │
    │  ├───────────┤   │ │  ├───────────┤  │
    │  │Producer-2 │   │ │  │Consumer-2 │  │
    │  └───────────┘   │ │  └───────────┘  │
    └──────────────────┘ └──────────────────┘

3.2 通信流程

3.2.1 启动流程
  1. NameServer 启动

    • 监听端口(默认 9876)
    • 初始化路由信息表
  2. Broker 启动

    • 加载配置文件
    • 启动各个服务模块
    • 向所有 NameServer 注册
    • 每 30s 发送心跳保持注册信息
  3. Producer 启动

    • 连接 NameServer
    • 获取 Topic 路由信息
    • 建立与 Broker 的连接
  4. Consumer 启动

    • 连接 NameServer
    • 获取 Topic 路由信息
    • 建立与 Broker 的连接
    • 订阅 Topic
    • 开始消费
3.2.2 消息发送流程
复制代码
Producer → NameServer: 获取路由信息
Producer → Broker: 发送消息
Broker → 磁盘: 持久化消息
Broker → Producer: 返回发送结果
3.2.3 消息消费流程
复制代码
Consumer → NameServer: 获取路由信息
Consumer → Broker: 拉取消息 (长轮询)
Broker → Consumer: 返回消息
Consumer → Broker: 提交消费进度

3.3 部署架构

3.3.1 单 Master 模式
复制代码
优点:配置简单,适合测试
缺点:单点故障,不可靠
3.3.2 多 Master 模式
复制代码
优点:高性能,简单
缺点:单台机器宕机期间,未消费的消息不可用
3.3.3 多 Master 多 Slave 模式(异步复制)
复制代码
Master → (异步复制) → Slave

优点:性能高,主备切换快
缺点:主机宕机可能丢失少量消息
3.3.4 多 Master 多 Slave 模式(同步双写)
复制代码
Master → (同步复制) → Slave

优点:数据可靠性高
缺点:性能略低,发送延迟增加

推荐生产环境配置

复制代码
2 Master + 2 Slave (同步双写) + 3 NameServer

四、底层原理深度解析

4.1 网络通信原理

4.1.1 Remoting 通信框架

RocketMQ 使用自研的 Remoting 模块,基于 Netty 实现。

核心组件

java 复制代码
RemotingServer  // 服务端
RemotingClient  // 客户端
RemotingCommand // 通信协议

通信协议格式

复制代码
┌─────────────────────────────────────────────────────┐
│  Length (4 bytes)  │  Header Length (4 bytes)      │
├─────────────────────────────────────────────────────┤
│                   Header Data                       │
│  (Serialized JSON or Custom Format)                 │
├─────────────────────────────────────────────────────┤
│                   Body Data                         │
│  (Message Body / Binary Data)                       │
└─────────────────────────────────────────────────────┘

协议字段

  • code:请求/响应码
  • language:语言标识
  • version:版本号
  • opaque:请求 ID(用于异步响应匹配)
  • flag:标志位(请求/响应、单向等)
  • remark:描述信息
  • extFields:扩展字段
4.1.2 长轮询机制

Consumer 拉取消息时,如果没有新消息,Broker 不会立即返回,而是:

java 复制代码
// 长轮询流程
1. Consumer 发起拉取请求
2. Broker 检查是否有新消息
   - 有新消息:立即返回
   - 无新消息:Hold 住请求(默认15s)
3. Hold 期间:
   - 有新消息到达:立即返回
   - 超时:返回无消息
4. Consumer 收到响应后,立即发起下一次拉取

优点

  • 减少无效的轮询请求
  • 降低消息消费延迟
  • 减少服务器压力

4.2 存储原理

4.2.1 零拷贝技术

RocketMQ 使用 mmap (Memory-Mapped File)sendfile 实现零拷贝。

传统IO流程(4次拷贝):

复制代码
磁盘 → 内核缓冲区 → 用户缓冲区 → Socket缓冲区 → 网卡
     (DMA拷贝)   (CPU拷贝)    (CPU拷贝)   (DMA拷贝)

mmap 优化(3次拷贝):

复制代码
磁盘 → 内核缓冲区(mmap映射到用户空间) → Socket缓冲区 → 网卡
     (DMA拷贝)                       (CPU拷贝)   (DMA拷贝)

sendfile 优化(2次拷贝):

复制代码
磁盘 → 内核缓冲区 → 网卡
     (DMA拷贝)   (DMA拷贝)

RocketMQ 的实现

java 复制代码
// CommitLog 使用 mmap
MappedFile mappedFile = new MappedFile(fileName, fileSize);
MappedByteBuffer mappedByteBuffer = mappedFile.getMappedByteBuffer();

// 消息发送时直接操作 MappedByteBuffer
mappedByteBuffer.put(messageBytes);
4.2.2 顺序写入

原理

  • CommitLog 文件顺序写入
  • 避免磁盘随机写入(随机写约 100KB/s,顺序写可达 600MB/s)
  • 利用操作系统 PageCache 缓存

写入流程

复制代码
消息 → 内存映射文件(MappedFile) → PageCache → 异步刷盘/同步刷盘 → 磁盘
4.2.3 文件预分配
java 复制代码
// CommitLog 文件默认大小 1GB
private static final int FILE_SIZE = 1024 * 1024 * 1024;

// 文件预热:提前申请物理内存
mlock(); // 锁定内存,防止被swap
madvise(MADV_WILLNEED); // 告知内核即将访问

4.3 消息过滤原理

4.3.1 Tag 过滤

Broker 端过滤

java 复制代码
// 存储时计算 Tag HashCode
int tagsCode = message.getTags().hashCode();

// 消费时比对 HashCode
if (subscriptionData.getCodeSet().contains(tagsCode)) {
    // HashCode 匹配,发送给 Consumer
}

Consumer 端二次过滤

java 复制代码
// 防止 Hash 冲突,Consumer 端再次比对
if (subscriptionData.getTagSet().contains(message.getTags())) {
    // 真正匹配,进行消费
}
4.3.2 SQL92 过滤

原理

java 复制代码
// 消息属性
message.putUserProperty("age", "18");
message.putUserProperty("vip", "true");

// 消费时使用 SQL 表达式过滤
consumer.subscribe("TopicTest",
    MessageSelector.bySql("age > 16 AND vip = 'true'"));

实现

  • 使用 JSQLParser 解析 SQL 表达式
  • 构建表达式树
  • 执行表达式计算

4.4 负载均衡原理

4.4.1 Producer 负载均衡

默认策略:轮询选择 MessageQueue

java 复制代码
// 轮询算法
public MessageQueue selectOneMessageQueue(final String topic) {
    int index = sendWhichQueue.getAndIncrement();
    int pos = Math.abs(index) % messageQueueList.size();
    return messageQueueList.get(pos);
}

故障延迟

java 复制代码
// 发送失败的 Broker 会暂时被隔离
// 默认隔离时间递增:1s, 2s, 3s...最长 10min
4.4.2 Consumer 负载均衡

负载均衡时机

  • Consumer 启动
  • Consumer 数量变化
  • Topic 队列数量变化
  • 定时重新负载均衡(默认 20s)

负载均衡算法

1. 平均分配算法(默认)

复制代码
假设: 8个队列, 3个Consumer
Consumer-0: Queue0, Queue1, Queue2
Consumer-1: Queue3, Queue4, Queue5
Consumer-2: Queue6, Queue7

2. 环形分配算法

复制代码
假设: 8个队列, 3个Consumer
Consumer-0: Queue0, Queue3, Queue6
Consumer-1: Queue1, Queue4, Queue7
Consumer-2: Queue2, Queue5

3. 机房分配算法

优先分配同机房的队列。

4. 一致性Hash算法

根据 Consumer 的 ClientId 进行 Hash 分配。

4.5 顺序消息原理

4.5.1 全局顺序

实现

  • Topic 只创建 1 个 MessageQueue
  • 只有 1 个 Consumer 消费
java 复制代码
// 创建只有1个队列的Topic
sh mqadmin updateTopic -c DefaultCluster -t OrderTopic -n 1

缺点:无法并发,性能低。

4.5.2 分区顺序

实现

  • 同一业务逻辑的消息发送到同一个 Queue
  • 同一个 Queue 只被一个 Consumer 消费线程消费
java 复制代码
// 发送时指定 MessageQueue 选择器
producer.send(message, new MessageQueueSelector() {
    @Override
    public MessageQueue select(List<MessageQueue> mqs,
                               Message msg, Object arg) {
        // arg 是订单ID
        Long orderId = (Long) arg;
        int index = (int) (orderId % mqs.size());
        return mqs.get(index);
    }
}, orderId);

消费端保证

java 复制代码
// 使用 MessageListenerOrderly
consumer.registerMessageListener(new MessageListenerOrderly() {
    @Override
    public ConsumeOrderlyStatus consumeMessage(
            List<MessageExt> msgs, ConsumeOrderlyContext context) {
        // 单线程顺序消费
        return ConsumeOrderlyStatus.SUCCESS;
    }
});

底层实现

  • Broker 对每个 Queue 加锁
  • Consumer 消费时获取队列锁
  • 消费完成后释放锁

4.6 事务消息原理

4.6.1 两阶段提交

流程

复制代码
1. Producer 发送半消息(Half Message)到 Broker
2. Broker 存储半消息,返回成功
3. Producer 执行本地事务
4. Producer 根据本地事务结果:
   - 成功:发送 Commit 请求
   - 失败:发送 Rollback 请求
5. Broker 收到 Commit:
   - 将半消息标记为可消费
   - Consumer 可以消费该消息
6. Broker 收到 Rollback:
   - 删除半消息
4.6.2 事务回查

场景:Producer 宕机,未发送 Commit/Rollback

流程

复制代码
1. Broker 定时扫描半消息(默认 60s)
2. 回查 Producer 的本地事务状态
3. Producer 返回事务状态:
   - COMMIT_MESSAGE:提交事务
   - ROLLBACK_MESSAGE:回滚事务
   - UNKNOW:稍后再次回查
4. Broker 根据回查结果处理半消息

实现原理

java 复制代码
// 半消息存储在特殊 Topic
String HALF_TOPIC = "RMQ_SYS_TRANS_HALF_TOPIC";

// 已提交消息的索引
String OP_TOPIC = "RMQ_SYS_TRANS_OP_HALF_TOPIC";

// 回查流程
1. 扫描 Half Topic
2. 检查消息是否在 OP Topic 中
3. 未提交的消息进行回查
4. 回查超过最大次数(15次),丢弃消息

五、数据存储机制

5.1 存储整体架构

复制代码
┌──────────────────────────────────────────────────────┐
│                 RocketMQ Store                        │
│                                                       │
│  ┌─────────────┐  ┌──────────────┐  ┌────────────┐  │
│  │ CommitLog   │  │ ConsumeQueue │  │ IndexFile  │  │
│  │  (消息存储)  │  │   (消费索引)  │  │ (索引文件)  │  │
│  └──────┬──────┘  └──────┬───────┘  └──────┬─────┘  │
│         │                │                 │         │
│         │                │                 │         │
│  ┌──────▼────────────────▼─────────────────▼─────┐  │
│  │              MappedFileQueue                   │  │
│  │          (映射文件队列管理)                      │  │
│  └──────┬─────────────────────────────────────────┘  │
│         │                                             │
│  ┌──────▼──────┐                                     │
│  │ MappedFile  │  MappedFile  ...  MappedFile       │
│  │   (mmap)    │                                     │
│  └─────────────┘                                     │
│                                                       │
│  ┌─────────────────────────────────────────────┐    │
│  │          FlushDiskService                    │    │
│  │   (刷盘服务: 同步/异步)                       │    │
│  └─────────────────────────────────────────────┘    │
│                                                       │
│  ┌─────────────────────────────────────────────┐    │
│  │          HAService                           │    │
│  │   (主从同步服务)                              │    │
│  └─────────────────────────────────────────────┘    │
└──────────────────────────────────────────────────────┘

5.2 核心存储文件

5.2.1 CommitLog(消息存储文件)

特点

  • 所有 Topic 的消息混合存储
  • 顺序写入,性能极高
  • 单个文件大小固定 1GB
  • 文件名:起始偏移量(20位,左补零)

文件示例

复制代码
${user.home}/store/commitlog/
├── 00000000000000000000  (0 - 1GB)
├── 00000000001073741824  (1GB - 2GB)
├── 00000000002147483648  (2GB - 3GB)
└── ...

消息存储格式

复制代码
┌────────────────────────────────────────────────────────┐
│  TOTALSIZE(4B)  │  消息总长度                          │
├────────────────────────────────────────────────────────┤
│  MAGICCODE(4B)  │  魔数标识: 0xdaa320a7               │
├────────────────────────────────────────────────────────┤
│  BODYCRC(4B)    │  消息体CRC校验                       │
├────────────────────────────────────────────────────────┤
│  QUEUEID(4B)    │  队列ID                              │
├────────────────────────────────────────────────────────┤
│  FLAG(4B)       │  消息标志                            │
├────────────────────────────────────────────────────────┤
│  QUEUEOFFSET(8B)│  队列偏移量                          │
├────────────────────────────────────────────────────────┤
│  PHYSICALOFFSET(8B) │  物理偏移量                     │
├────────────────────────────────────────────────────────┤
│  SYSFLAG(4B)    │  系统标志                            │
├────────────────────────────────────────────────────────┤
│  BORNTIMESTAMP(8B)  │  消息产生时间戳                 │
├────────────────────────────────────────────────────────┤
│  BORNHOST(8B)   │  生产者地址(IP+端口)                 │
├────────────────────────────────────────────────────────┤
│  STORETIMESTAMP(8B) │  消息存储时间戳                 │
├────────────────────────────────────────────────────────┤
│  STOREHOSTADDRESS(8B)│ Broker地址                     │
├────────────────────────────────────────────────────────┤
│  RECONSUMETIMES(4B) │  重消费次数                     │
├────────────────────────────────────────────────────────┤
│  PreparedTransactionOffset(8B) │ 事务消息偏移        │
├────────────────────────────────────────────────────────┤
│  MessageBody Length(4B) + Body  │  消息体            │
├────────────────────────────────────────────────────────┤
│  Topic Length(1B) + Topic       │  主题              │
├────────────────────────────────────────────────────────┤
│  Properties Length(2B) + Properties │ 扩展属性       │
└────────────────────────────────────────────────────────┘

关键字段说明

  • TOTALSIZE:当前消息的总字节数
  • QUEUEOFFSET:消息在 ConsumeQueue 中的逻辑偏移量
  • PHYSICALOFFSET:消息在 CommitLog 中的物理偏移量
  • SYSFLAG:包含消息类型(普通、事务、延迟)等信息
5.2.2 ConsumeQueue(消费队列文件)

作用

  • CommitLog 的索引文件
  • 每个 Topic-QueueId 对应一个 ConsumeQueue
  • 提高消费效率

存储结构

复制代码
${user.home}/store/consumequeue/
├── TopicA
│   ├── 0
│   │   ├── 00000000000000000000
│   │   └── 00000000000000300000
│   ├── 1
│   │   └── 00000000000000000000
│   └── 2
│       └── 00000000000000000000
└── TopicB
    └── 0
        └── 00000000000000000000

单个文件大小

  • 默认 30W 条索引(可配置)
  • 每条索引 20 字节
  • 文件大小 = 30W * 20B = 6MB

索引条目格式(固定 20 字节):

复制代码
┌──────────────────────────────────────────┐
│  CommitLog Offset(8B)  │  消息物理偏移  │
├──────────────────────────────────────────┤
│  Size(4B)              │  消息大小      │
├──────────────────────────────────────────┤
│  Message Tag HashCode(8B) │ Tag哈希码  │
└──────────────────────────────────────────┘

查找流程

复制代码
1. 根据 Topic + QueueId 找到 ConsumeQueue
2. 根据 消费偏移量 定位索引条目
3. 读取 CommitLog Offset
4. 从 CommitLog 读取完整消息
5.2.3 IndexFile(索引文件)

作用

  • 通过 Key 或时间区间查询消息
  • 可选功能,影响性能

存储结构

复制代码
${user.home}/store/index/
├── 20231101152030000
├── 20231101163045000
└── 20231101174050000

文件格式

复制代码
┌────────────────────────────────────────────────────┐
│  Header (40 Bytes)                                 │
│  ┌──────────────────────────────────────────┐     │
│  │ BeginTimestamp (8B)  │ 文件第一条消息时间│     │
│  │ EndTimestamp (8B)    │ 文件最后消息时间  │     │
│  │ BeginPhyOffset (8B)  │ 第一条消息偏移    │     │
│  │ EndPhyOffset (8B)    │ 最后消息偏移      │     │
│  │ HashSlotCount (4B)   │ Hash槽数量        │     │
│  │ IndexCount (4B)      │ 索引条目数量      │     │
│  └──────────────────────────────────────────┘     │
├────────────────────────────────────────────────────┤
│  Hash Slot Array (500W * 4B = 20MB)               │
│  每个槽存储该 Hash 值的最新索引条目位置            │
├────────────────────────────────────────────────────┤
│  Index Linked List (2000W * 20B = 400MB)          │
│  ┌──────────────────────────────────────────┐     │
│  │ KeyHash (4B)         │ Key的Hash值       │     │
│  │ PhyOffset (8B)       │ CommitLog偏移     │     │
│  │ TimeDiff (4B)        │ 时间差            │     │
│  │ NextIndexOffset (4B) │ 下一个索引位置    │     │
│  └──────────────────────────────────────────┘     │
└────────────────────────────────────────────────────┘

查找流程

复制代码
1. 计算 Key 的 HashCode
2. HashCode % 500W = slotPos (槽位置)
3. 读取 Hash Slot,获取索引条目位置
4. 遍历链表,比对 KeyHash 和时间
5. 找到匹配的 PhyOffset
6. 从 CommitLog 读取消息
5.2.4 其他文件

checkpoint(检查点文件)

复制代码
${user.home}/store/checkpoint

存储最后一次刷盘时间戳,用于恢复。

config/

  • topics.json:Topic 配置
  • subscriptionGroup.json:消费组配置
  • delayOffset.json:延迟消息进度
  • consumerOffset.json:消费进度(集群模式)

abort

异常退出标记文件,启动时检查。

5.3 刷盘机制

5.3.1 同步刷盘

流程

复制代码
消息写入 PageCache → 立即调用 fsync → 刷盘完成 → 返回成功

配置

properties 复制代码
flushDiskType=SYNC_FLUSH

特点

  • 可靠性高,消息不丢失
  • 性能较低(每秒约 1000 TPS)

实现

java 复制代码
// 消息写入 MappedByteBuffer
mappedByteBuffer.put(messageBytes);

// GroupCommitService 处理刷盘
// 唤醒刷盘线程
GroupCommitRequest request = new GroupCommitRequest(offset);
this.requestsWrite.add(request);
this.wakeup();

// 等待刷盘完成
request.waitForFlush(5000); // 超时时间 5s
5.3.2 异步刷盘

流程

复制代码
消息写入 PageCache → 立即返回成功 → 后台线程定时刷盘

配置

properties 复制代码
flushDiskType=ASYNC_FLUSH
# 刷盘间隔(毫秒)
flushIntervalCommitLog=500
# 每次刷盘最少页数
flushCommitLogLeastPages=4

特点

  • 性能高(每秒数万 TPS)
  • 异常情况可能丢失部分消息(毫秒级)

两种实现

1. FlushRealTimeService(实时刷盘)

java 复制代码
// 定时刷盘
while (!this.isStopped()) {
    Thread.sleep(interval); // 默认500ms
    this.mappedFileQueue.flush(flushLeastPages);
}

2. CommitRealTimeService(两阶段刷盘)

java 复制代码
// 使用 TransientStorePool(堆外内存池)
// 第一阶段:MappedByteBuffer → WriteBuffer
// 第二阶段:WriteBuffer → FileChannel → 磁盘

// 优点:减少锁竞争,提高并发性能
5.3.3 刷盘对比
对比项 同步刷盘 异步刷盘
性能 低(1K TPS) 高(数万 TPS)
可靠性 高(不丢消息) 较低(宕机可能丢消息)
适用场景 金融、支付等 日志、监控等
响应延迟 高(毫秒级) 低(微秒级)

5.4 主从同步机制

5.4.1 同步策略

1. 异步复制(ASYNC_MASTER)

复制代码
Master 写入成功 → 立即返回 → 异步同步到 Slave

2. 同步双写(SYNC_MASTER)

复制代码
Master 写入成功 → 等待 Slave 同步成功 → 返回成功

配置

properties 复制代码
# Master 配置
brokerRole=SYNC_MASTER  # 或 ASYNC_MASTER
brokerId=0

# Slave 配置
brokerRole=SLAVE
brokerId=1  # 大于0
5.4.2 HAService 实现

核心组件

java 复制代码
HAService
├── AcceptSocketService      // 接受 Slave 连接
├── GroupTransferService     // 主从同步进度管理
└── HAConnection             // 主从连接
    ├── ReadSocketService    // 读取 Slave 反馈
    └── WriteSocketService   // 向 Slave 写数据

同步流程

复制代码
1. Slave 启动,连接 Master
2. Slave 上报已同步的 Offset
3. Master 从该 Offset 开始传输数据
4. Slave 接收数据,写入本地 CommitLog
5. Slave 上报新的 Offset
6. 循环执行 3-5

同步协议

复制代码
┌─────────────────────────────────────┐
│  Master → Slave                     │
├─────────────────────────────────────┤
│  PhyOffset (8B)  │  开始物理偏移   │
│  BodySize (4B)   │  数据大小       │
│  Body Data       │  CommitLog数据  │
└─────────────────────────────────────┘

┌─────────────────────────────────────┐
│  Slave → Master (ACK)               │
├─────────────────────────────────────┤
│  MaxOffset (8B)  │  当前最大偏移   │
└─────────────────────────────────────┘
5.4.3 读写分离

读写策略

properties 复制代码
# Slave 允许读取的最大延迟(字节)
slaveReadEnable=true
accessMessageInMemoryMaxRatio=40  # 内存占用超过40%,建议从Slave读

消费时的选择

复制代码
1. 优先从 Master 读取
2. Master 压力大时,从 Slave 读取:
   - Master 内存占用 > 40%
   - Master 与 Slave 同步延迟 < 阈值
3. Slave 挂掉,自动切换回 Master

5.5 消息清理机制

5.5.1 过期清理

触发条件(满足任一):

  1. 默认凌晨 4 点自动删除
  2. 磁盘空间不足(超过 75%)
  3. 手动触发清理

保留时间

properties 复制代码
# 文件保留时间(小时)
fileReservedTime=72  # 默认72小时

清理流程

复制代码
1. 检查文件最后修改时间
2. 当前时间 - 修改时间 > 保留时间
3. 文件未被使用(引用计数=0)
4. 删除文件
5.5.2 空间清理

磁盘水位控制

properties 复制代码
# 磁盘空间警戒水位
diskMaxUsedSpaceRatio=75  # 超过75%开始清理

# 强制清理水位
diskSpaceCleanForciblyRatio=85  # 超过85%立即清理

清理策略

复制代码
水位 < 75%:正常运行
75% < 水位 < 85%:清理过期文件
85% < 水位 < 90%:清理最早文件(无视过期时间)
90% < 水位:拒绝写入,报警

六、数据结构详解

6.1 内存映射文件

6.1.1 MappedFile

定义:单个映射文件的抽象。

java 复制代码
public class MappedFile {
    // 文件名
    private String fileName;

    // 文件起始偏移量
    private long fileFromOffset;

    // 映射的 ByteBuffer
    private MappedByteBuffer mappedByteBuffer;

    // 文件通道
    private FileChannel fileChannel;

    // 当前写位置
    private AtomicInteger wrotePosition = new AtomicInteger(0);

    // 当前提交位置(TransientStorePool 模式)
    private AtomicInteger committedPosition = new AtomicInteger(0);

    // 当前刷盘位置
    private AtomicInteger flushedPosition = new AtomicInteger(0);

    // 文件大小
    private int fileSize;
}

关键方法

java 复制代码
// 追加消息
public AppendMessageResult appendMessage(MessageExtBrokerInner msg);

// 刷盘
public int flush(final int flushLeastPages);

// 获取数据
public SelectMappedBufferResult selectMappedBuffer(int pos, int size);
6.1.2 MappedFileQueue

定义:管理一组连续的 MappedFile。

java 复制代码
public class MappedFileQueue {
    // 存储路径
    private String storePath;

    // 单个文件大小
    private int mappedFileSize;

    // 文件列表(有序)
    private CopyOnWriteArrayList<MappedFile> mappedFiles;

    // 刷盘位置
    private long flushedWhere = 0;

    // 提交位置
    private long committedWhere = 0;
}

关键方法

java 复制代码
// 获取最后一个文件
public MappedFile getLastMappedFile();

// 根据偏移量获取文件
public MappedFile findMappedFileByOffset(final long offset);

// 删除过期文件
public int deleteExpiredFileByTime(long expiredTime);

6.2 消息数据结构

6.2.1 Message

生产者消息

java 复制代码
public class Message {
    private String topic;                    // 主题
    private int flag;                        // 标志
    private Map<String, String> properties; // 属性
    private byte[] body;                     // 消息体
    private String transactionId;           // 事务ID

    // 关键属性
    public static final String PROPERTY_KEYS = "KEYS";           // 索引键
    public static final String PROPERTY_TAGS = "TAGS";           // 标签
    public static final String PROPERTY_DELAY_TIME_LEVEL = "DELAY"; // 延迟级别
    public static final String PROPERTY_WAIT_STORE_MSG_OK = "WAIT"; // 等待存储
}
6.2.2 MessageExt

扩展消息(消费者接收)

java 复制代码
public class MessageExt extends Message {
    private int queueId;                    // 队列ID
    private int storeSize;                  // 存储大小
    private long queueOffset;               // 队列偏移量
    private int sysFlag;                    // 系统标志
    private long bornTimestamp;             // 产生时间
    private SocketAddress bornHost;         // 产生主机
    private long storeTimestamp;            // 存储时间
    private SocketAddress storeHost;        // 存储主机
    private String msgId;                   // 消息ID
    private long commitLogOffset;           // CommitLog偏移量
    private int bodyCRC;                    // CRC校验
    private int reconsumeTimes;             // 重消费次数
}
6.2.3 MessageExtBrokerInner

Broker 内部消息

java 复制代码
public class MessageExtBrokerInner extends MessageExt {
    private String propertiesString;        // 属性字符串
    private long tagsCode;                  // Tag哈希码
    private MessageAccessor accessor;       // 访问器
}

6.3 路由数据结构

6.3.1 TopicRouteData
java 复制代码
public class TopicRouteData {
    private String orderTopicConf;                    // 顺序消息配置
    private List<QueueData> queueDatas;               // 队列数据
    private List<BrokerData> brokerDatas;             // Broker数据
    private HashMap<String, List<String>> filterServerTable; // 过滤服务器
}
6.3.2 QueueData
java 复制代码
public class QueueData {
    private String brokerName;     // Broker名称
    private int readQueueNums;     // 读队列数量
    private int writeQueueNums;    // 写队列数量
    private int perm;              // 读写权限
    private int topicSysFlag;      // Topic系统标志
}
6.3.3 BrokerData
java 复制代码
public class BrokerData {
    private String cluster;                          // 集群名称
    private String brokerName;                       // Broker名称
    private HashMap<Long, String> brokerAddrs;       // Broker地址 (brokerId -> address)
}

6.4 消费数据结构

6.4.1 ProcessQueue

消费处理队列

java 复制代码
public class ProcessQueue {
    // 消息树(有序)
    private TreeMap<Long, MessageExt> msgTreeMap = new TreeMap<>();

    // 读写锁
    private ReadWriteLock lockTreeMap = new ReentrantReadWriteLock();

    // 消息总数
    private AtomicLong msgCount = new AtomicLong();

    // 消息总大小
    private AtomicLong msgSize = new AtomicLong();

    // 最大偏移量
    private volatile long queueOffsetMax = 0L;

    // 是否被丢弃
    private volatile boolean dropped = false;

    // 最后拉取时间
    private volatile long lastPullTimestamp = System.currentTimeMillis();

    // 最后消费时间
    private volatile long lastConsumeTimestamp = System.currentTimeMillis();
}
6.4.2 ConsumeQueue (逻辑概念)

消费队列数据结构

java 复制代码
public class ConsumeQueue {
    private String topic;              // 主题
    private int queueId;               // 队列ID
    private MappedFileQueue mappedFileQueue; // 文件队列
    private String storePath;          // 存储路径

    // 索引条目大小:20字节
    public static final int CQ_STORE_UNIT_SIZE = 20;

    // 单个文件条目数:30万
    public static final int CQ_UNIT_COUNT = 300000;
}

6.5 重要工具类

6.5.1 MessageDecoder

消息编解码器

java 复制代码
public class MessageDecoder {
    // 消息魔数
    public static final int MESSAGE_MAGIC_CODE = 0xdaa320a7;

    // 消息存储固定长度
    public static final int MESSAGE_STORE_FIXED_SIZE =
        4 + // TOTALSIZE
        4 + // MAGICCODE
        4 + // BODYCRC
        4 + // QUEUEID
        4 + // FLAG
        8 + // QUEUEOFFSET
        8 + // PHYSICALOFFSET
        4 + // SYSFLAG
        8 + // BORNTIMESTAMP
        8 + // BORNHOST
        8 + // STORETIMESTAMP
        8 + // STOREHOSTADDRESS
        4 + // RECONSUMETIMES
        8;  // Prepared Transaction Offset

    // 解码消息
    public static MessageExt decode(ByteBuffer byteBuffer);

    // 创建消息ID
    public static String createMessageId(ByteBuffer buffer, long offset);
}
6.5.2 UtilAll

工具类

java 复制代码
public class UtilAll {
    // 计算下一个文件起始偏移量
    public static long computeNextBeginOffset(
        long currentOffset, int fileSize);

    // 偏移量转文件名
    public static String offset2FileName(long offset);

    // 文件名转偏移量
    public static long fileName2Offset(String fileName);
}

七、消息发送流程

7.1 Producer 启动流程

java 复制代码
// 1. 创建 Producer
DefaultMQProducer producer = new DefaultMQProducer("ProducerGroup");
producer.setNamesrvAddr("localhost:9876");

// 2. 启动 Producer
producer.start();

启动流程详解

复制代码
1. 检查 producerGroup 是否合法
2. 创建 MQClientInstance(JVM单例)
3. 向 MQClientInstance 注册 Producer
4. 启动 MQClientInstance:
   - 启动请求响应通道
   - 启动定时任务:
     * 定时从 NameServer 更新路由信息(30s)
     * 定时向 Broker 发送心跳(30s)
     * 定时持久化消费进度(5s)
     * 定时调整线程池
5. 向所有 Broker 发送心跳

7.2 消息发送方式

7.2.1 同步发送
java 复制代码
Message msg = new Message("TopicTest", "TagA", "Hello RocketMQ".getBytes());
SendResult sendResult = producer.send(msg);
System.out.println(sendResult);

流程

复制代码
1. 发送消息
2. 等待 Broker 返回
3. 返回 SendResult

适用场景

  • 重要通知消息
  • 短信通知
  • 需要确保消息送达的场景
7.2.2 异步发送
java 复制代码
producer.send(msg, new SendCallback() {
    @Override
    public void onSuccess(SendResult sendResult) {
        System.out.println("发送成功:" + sendResult);
    }

    @Override
    public void onException(Throwable e) {
        System.out.println("发送失败:" + e.getMessage());
    }
});

流程

复制代码
1. 发送消息
2. 立即返回
3. 回调 SendCallback

适用场景

  • 对响应时间敏感
  • 高并发场景
7.2.3 单向发送
java 复制代码
producer.sendOneway(msg);

流程

复制代码
1. 发送消息
2. 立即返回
3. 不关心结果

适用场景

  • 日志收集
  • 可靠性要求不高的场景

7.3 消息发送流程详解

7.3.1 路由查找
java 复制代码
// 1. 本地缓存查找
TopicPublishInfo publishInfo =
    this.topicPublishInfoTable.get(topic);

// 2. 缓存未命中,从 NameServer 获取
if (publishInfo == null || !publishInfo.ok()) {
    this.mQClientFactory.updateTopicRouteInfoFromNameServer(topic);
    publishInfo = this.topicPublishInfoTable.get(topic);
}

// 3. 使用默认 Topic 路由
if (publishInfo == null || !publishInfo.ok()) {
    this.mQClientFactory.updateTopicRouteInfoFromNameServer(
        DEFAULT_TOPIC, true, this.defaultMQProducer);
    publishInfo = this.topicPublishInfoTable.get(topic);
}
7.3.2 队列选择

默认策略(轮询)

java 复制代码
public MessageQueue selectOneMessageQueue(
        final TopicPublishInfo tpInfo,
        final String lastBrokerName) {
    // 故障延迟未启用
    if (!this.sendLatencyFaultEnable) {
        int index = tpInfo.getSendWhichQueue().getAndIncrement();
        int pos = Math.abs(index) % tpInfo.getMessageQueueList().size();
        return tpInfo.getMessageQueueList().get(pos);
    }

    // 故障延迟启用:避开上次失败的Broker
    for (int i = 0; i < tpInfo.getMessageQueueList().size(); i++) {
        int pos = Math.abs(index++) % tpInfo.getMessageQueueList().size();
        MessageQueue mq = tpInfo.getMessageQueueList().get(pos);
        if (!mq.getBrokerName().equals(lastBrokerName)) {
            return mq;
        }
    }

    // 实在没有可用的,选一个延迟最小的Broker
    return pickOneAtLeast();
}

故障延迟机制

java 复制代码
// 记录发送延迟
public void updateFaultItem(
        String brokerName,
        long currentLatency,
        boolean isolation) {

    // 根据延迟时间计算规避时长
    long duration = computeNotAvailableDuration(
        isolation ? 30000 : currentLatency);

    this.latencyFaultTolerance.updateFaultItem(
        brokerName, currentLatency, duration);
}

// 延迟级别表
private long[] latencyMax = {50L, 100L, 550L, 1000L, 2000L, 3000L, 15000L};
private long[] notAvailableDuration = {0L, 0L, 30000L, 60000L, 120000L, 180000L, 600000L};
7.3.3 消息发送
java 复制代码
// 发送消息的核心方法
private SendResult sendDefaultImpl(
        Message msg,
        final CommunicationMode communicationMode,
        final SendCallback sendCallback,
        final long timeout) {

    // 1. 参数验证
    Validators.checkMessage(msg, this);

    // 2. 查找路由
    TopicPublishInfo publishInfo =
        this.tryToFindTopicPublishInfo(msg.getTopic());

    // 3. 重试发送(最多3次)
    int timesTotal = 1 + this.defaultMQProducer.getRetryTimesWhenSendFailed();
    for (int times = 0; times < timesTotal; times++) {
        // 选择队列
        MessageQueue mq = this.selectOneMessageQueue(publishInfo, lastBrokerName);

        try {
            // 发送消息
            sendResult = this.sendKernelImpl(
                msg, mq, communicationMode,
                sendCallback, publishInfo, timeout);

            // 发送成功,返回结果
            return sendResult;

        } catch (RemotingException e) {
            // 异常处理,继续重试
            continue;
        } catch (MQClientException e) {
            // Broker 异常,切换 Broker 重试
            lastBrokerName = mq.getBrokerName();
            continue;
        }
    }

    // 重试失败,抛出异常
    throw new MQClientException("Send message failed");
}
7.3.4 消息发送到 Broker
java 复制代码
private SendResult sendKernelImpl(
        final Message msg,
        final MessageQueue mq,
        final CommunicationMode communicationMode,
        final SendCallback sendCallback,
        final TopicPublishInfo topicPublishInfo,
        final long timeout) {

    // 1. 查找 Broker 地址
    String brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(
        mq.getBrokerName());

    // 2. 构建请求头
    SendMessageRequestHeader requestHeader = new SendMessageRequestHeader();
    requestHeader.setProducerGroup(this.defaultMQProducer.getProducerGroup());
    requestHeader.setTopic(msg.getTopic());
    requestHeader.setDefaultTopic(DEFAULT_TOPIC);
    requestHeader.setDefaultTopicQueueNums(this.defaultMQProducer.getDefaultTopicQueueNums());
    requestHeader.setQueueId(mq.getQueueId());
    requestHeader.setSysFlag(sysFlag);
    requestHeader.setBornTimestamp(System.currentTimeMillis());
    requestHeader.setFlag(msg.getFlag());
    requestHeader.setProperties(MessageDecoder.messageProperties2String(msg.getProperties()));

    // 3. 发送消息
    switch (communicationMode) {
        case SYNC:
            // 同步发送
            return this.mQClientFactory.getMQClientAPIImpl().sendMessage(
                brokerAddr, mq.getBrokerName(), msg, requestHeader,
                timeout, communicationMode, sendCallback, topicPublishInfo,
                this.mQClientFactory, retryTimesWhenSendFailed, context, this);
        case ASYNC:
            // 异步发送
            this.mQClientFactory.getMQClientAPIImpl().sendMessage(...);
            return null;
        case ONEWAY:
            // 单向发送
            this.mQClientFactory.getMQClientAPIImpl().sendMessage(...);
            return null;
    }
}

7.4 Broker 接收消息

java 复制代码
// Broker 处理发送请求
public RemotingCommand processRequest(ChannelHandlerContext ctx,
                                       RemotingCommand request) {
    // 1. 解析请求
    SendMessageRequestHeader requestHeader =
        parseRequestHeader(request);

    // 2. 消息校验
    MessageExtBrokerInner msgInner = new MessageExtBrokerInner();
    msgInner.setTopic(requestHeader.getTopic());
    msgInner.setQueueId(requestHeader.getQueueId());
    msgInner.setBody(request.getBody());
    // ... 设置其他属性

    // 3. 存储消息
    PutMessageResult putMessageResult =
        this.brokerController.getMessageStore().putMessage(msgInner);

    // 4. 返回结果
    return handlePutMessageResult(putMessageResult, request);
}

7.5 SendResult

发送结果

java 复制代码
public class SendResult {
    private SendStatus sendStatus;      // 发送状态
    private String msgId;               // 消息ID
    private MessageQueue messageQueue;  // 消息队列
    private long queueOffset;           // 队列偏移量
    private String transactionId;       // 事务ID
    private String offsetMsgId;         // 消息偏移ID
    private String regionId;            // 区域ID
    private boolean traceOn;            // 是否开启轨迹
}

发送状态

java 复制代码
public enum SendStatus {
    SEND_OK,                // 发送成功
    FLUSH_DISK_TIMEOUT,     // 刷盘超时
    FLUSH_SLAVE_TIMEOUT,    // 主从同步超时
    SLAVE_NOT_AVAILABLE,    // Slave不可用
}

八、消息消费流程

8.1 Consumer 启动流程

java 复制代码
// 1. 创建 Consumer
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("ConsumerGroup");
consumer.setNamesrvAddr("localhost:9876");

// 2. 订阅 Topic
consumer.subscribe("TopicTest", "*");

// 3. 注册消息监听器
consumer.registerMessageListener(new MessageListenerConcurrently() {
    @Override
    public ConsumeConcurrentlyStatus consumeMessage(
            List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
        System.out.println("收到消息:" + msgs);
        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
    }
});

// 4. 启动 Consumer
consumer.start();

启动流程详解

复制代码
1. 检查配置合法性
2. 复制订阅信息
3. 初始化 MQClientInstance
4. 初始化 RebalanceImpl(负载均衡)
5. 初始化 PullAPIWrapper(拉取API包装)
6. 初始化 OffsetStore(消费进度存储):
   - 集群模式:RemoteBrokerOffsetStore(存储在Broker)
   - 广播模式:LocalFileOffsetStore(本地文件)
7. 启动 ConsumeMessageService(消费服务):
   - 并发消费:ConsumeMessageConcurrentlyService
   - 顺序消费:ConsumeMessageOrderlyService
8. 向所有 Broker 注册 Consumer
9. 启动 MQClientInstance
10. 立即触发一次负载均衡

8.2 消息拉取流程

8.2.1 PullMessageService

拉取服务(后台线程)

java 复制代码
public class PullMessageService extends ServiceThread {
    // 拉取请求队列
    private LinkedBlockingQueue<PullRequest> pullRequestQueue
        = new LinkedBlockingQueue<>();

    @Override
    public void run() {
        while (!this.isStopped()) {
            try {
                // 从队列获取拉取请求
                PullRequest pullRequest = this.pullRequestQueue.take();

                // 执行拉取
                this.pullMessage(pullRequest);

            } catch (InterruptedException ignored) {
            }
        }
    }
}
8.2.2 拉取请求
java 复制代码
public class PullRequest {
    private String consumerGroup;       // 消费组
    private MessageQueue messageQueue;  // 消息队列
    private ProcessQueue processQueue;  // 处理队列
    private long nextOffset;            // 下一次拉取偏移量
}
8.2.3 拉取流程
java 复制代码
public void pullMessage(final PullRequest pullRequest) {
    // 1. 获取处理队列
    ProcessQueue processQueue = pullRequest.getProcessQueue();

    // 2. 流控检查
    if (processQueue.getMsgCount().get() > 1000) {
        // 消息堆积过多,延迟拉取
        this.executePullRequestLater(pullRequest, 50);
        return;
    }

    // 3. 构建拉取参数
    PullCallback pullCallback = new PullCallback() {
        @Override
        public void onSuccess(PullResult pullResult) {
            switch (pullResult.getPullStatus()) {
                case FOUND:
                    // 拉取到消息
                    processQueue.putMessage(pullResult.getMsgFoundList());
                    // 提交消费请求
                    submitConsumeRequest(pullResult.getMsgFoundList(),
                        processQueue, pullRequest.getMessageQueue());
                    // 继续拉取
                    pullRequest.setNextOffset(pullResult.getNextBeginOffset());
                    pullMessageService.executePullRequestImmediately(pullRequest);
                    break;
                case NO_NEW_MSG:
                case NO_MATCHED_MSG:
                    // 无新消息,继续拉取
                    pullRequest.setNextOffset(pullResult.getNextBeginOffset());
                    pullMessageService.executePullRequestImmediately(pullRequest);
                    break;
                case OFFSET_ILLEGAL:
                    // 偏移量非法
                    pullRequest.getProcessQueue().setDropped(true);
                    break;
            }
        }

        @Override
        public void onException(Throwable e) {
            // 异常处理,延迟拉取
            pullMessageService.executePullRequestLater(pullRequest, 3000);
        }
    };

    // 4. 拉取消息
    this.pullAPIWrapper.pullKernelImpl(
        pullRequest.getMessageQueue(),
        subExpression,
        subscriptionData.getExpressionType(),
        subscriptionData.getSubVersion(),
        pullRequest.getNextOffset(),
        32,  // 批量拉取数量
        sysFlag,
        0,   // commitOffset
        BROKER_SUSPEND_MAX_TIME_MILLIS,  // 15s
        CONSUMER_TIMEOUT_MILLIS_WHEN_SUSPEND,  // 30s
        CommunicationMode.ASYNC,
        pullCallback
    );
}
8.2.4 Broker 处理拉取请求
java 复制代码
public RemotingCommand processRequest(ChannelHandlerContext ctx,
                                       RemotingCommand request) {
    // 1. 解析请求
    PullMessageRequestHeader requestHeader =
        parseRequestHeader(request);

    // 2. 查找消息
    GetMessageResult getMessageResult =
        this.brokerController.getMessageStore().getMessage(
            requestHeader.getConsumerGroup(),
            requestHeader.getTopic(),
            requestHeader.getQueueId(),
            requestHeader.getQueueOffset(),
            requestHeader.getMaxMsgNums(),
            subscriptionData
        );

    // 3. 处理结果
    switch (getMessageResult.getStatus()) {
        case FOUND:
            // 找到消息,立即返回
            response.setCode(ResponseCode.SUCCESS);
            break;
        case NO_MESSAGE_IN_QUEUE:
        case NO_MATCHED_MESSAGE:
            // 无消息,长轮询
            if (brokerAllowSuspend && hasSuspendFlag) {
                // Hold 请求
                PullRequest pullRequest = new PullRequest(
                    request, ctx, requestHeader);
                this.pullRequestHoldService.suspendPullRequest(
                    topic, queueId, pullRequest);
                response = null;  // 不返回
            } else {
                // 立即返回无消息
                response.setCode(ResponseCode.PULL_NOT_FOUND);
            }
            break;
        case OFFSET_FOUND_NULL:
        case OFFSET_OVERFLOW_ONE:
        case OFFSET_TOO_SMALL:
            // 偏移量异常
            response.setCode(ResponseCode.PULL_OFFSET_MOVED);
            break;
    }

    return response;
}

8.3 消息消费流程

8.3.1 并发消费
java 复制代码
class ConsumeMessageConcurrentlyService {
    // 消费线程池
    private ThreadPoolExecutor consumeExecutor;

    public void submitConsumeRequest(
            final List<MessageExt> msgs,
            final ProcessQueue processQueue,
            final MessageQueue messageQueue) {

        // 批量消费(默认1条)
        final int consumeBatchSize =
            this.defaultMQPushConsumer.getConsumeMessageBatchMaxSize();

        // 分批提交
        for (int i = 0; i < msgs.size(); ) {
            List<MessageExt> subList = new ArrayList<>();
            for (int j = 0; j < consumeBatchSize && i < msgs.size(); j++, i++) {
                subList.add(msgs.get(i));
            }

            // 提交消费任务
            ConsumeRequest consumeRequest = new ConsumeRequest(
                subList, processQueue, messageQueue);
            this.consumeExecutor.submit(consumeRequest);
        }
    }
}

消费任务

java 复制代码
class ConsumeRequest implements Runnable {
    @Override
    public void run() {
        // 1. 执行消费
        ConsumeConcurrentlyStatus status =
            listener.consumeMessage(msgs, context);

        // 2. 处理消费结果
        if (status == ConsumeConcurrentlyStatus.CONSUME_SUCCESS) {
            // 消费成功
            processConsumeResult(ConsumeStatus.SUCCESS, context);
        } else {
            // 消费失败
            processConsumeResult(ConsumeStatus.FAIL, context);
        }
    }

    public void processConsumeResult(
            ConsumeStatus status,
            ConsumeConcurrentlyContext context) {

        if (status == ConsumeStatus.SUCCESS) {
            // 移除已消费消息
            processQueue.removeMessage(msgs);
        } else {
            // 失败消息发送回 Broker(延迟重试)
            for (MessageExt msg : msgs) {
                this.sendMessageBack(msg, context.getDelayLevelWhenNextConsume());
            }
        }

        // 更新消费进度
        long offset = processQueue.removeMessage(msgs);
        this.offsetStore.updateOffset(messageQueue, offset, false);
    }
}
8.3.2 顺序消费
java 复制代码
class ConsumeMessageOrderlyService {
    public void submitConsumeRequest(
            final List<MessageExt> msgs,
            final ProcessQueue processQueue,
            final MessageQueue messageQueue) {

        // 顺序消费一次提交一批
        ConsumeRequest consumeRequest = new ConsumeRequest(
            processQueue, messageQueue);
        this.consumeExecutor.submit(consumeRequest);
    }
}

顺序消费任务

java 复制代码
class ConsumeRequest implements Runnable {
    @Override
    public void run() {
        // 1. 获取队列锁
        Object objLock = messageQueueLock.fetchLockObject(messageQueue);

        synchronized (objLock) {
            // 2. 获取 Broker 锁(集群模式)
            if (MessageModel.BROADCASTING.equals(consumeType)
                || this.lock(messageQueue)) {

                // 3. 循环消费
                while (!processQueue.isDropped()) {
                    // 获取消息
                    List<MessageExt> msgs = processQueue.takeMessages(consumeBatchSize);

                    if (msgs.isEmpty()) {
                        break;
                    }

                    // 执行消费
                    ConsumeOrderlyStatus status =
                        listener.consumeMessage(msgs, context);

                    // 处理结果
                    if (status == ConsumeOrderlyStatus.SUCCESS) {
                        processQueue.commit();
                    } else {
                        // 暂停消费,稍后重试
                        processQueue.makeMessageToConsumeAgain(msgs);
                        this.submitConsumeRequestLater(10);
                        break;
                    }
                }

                // 4. 释放 Broker 锁
                this.unlock(messageQueue);
            } else {
                // 未获取到锁,稍后重试
                this.submitConsumeRequestLater(100);
            }
        }
    }
}

8.4 消费进度管理

8.4.1 集群模式

存储在 Broker

java 复制代码
public class RemoteBrokerOffsetStore implements OffsetStore {
    // 本地缓存
    private ConcurrentMap<MessageQueue, AtomicLong> offsetTable;

    @Override
    public void updateOffset(MessageQueue mq, long offset, boolean increaseOnly) {
        // 更新本地缓存
        AtomicLong offsetOld = this.offsetTable.get(mq);
        if (offsetOld == null) {
            offsetOld = this.offsetTable.putIfAbsent(mq, new AtomicLong(offset));
        }
        if (offsetOld != null) {
            if (increaseOnly) {
                // 只增不减
                offsetOld.getAndUpdate(old -> Math.max(old, offset));
            } else {
                offsetOld.set(offset);
            }
        }
    }

    @Override
    public void persistAll(Set<MessageQueue> mqs) {
        // 批量提交到 Broker
        for (MessageQueue mq : mqs) {
            AtomicLong offset = this.offsetTable.get(mq);
            if (offset != null) {
                this.updateConsumeOffsetToBroker(mq, offset.get());
            }
        }
    }
}

定时持久化

java 复制代码
// 定时任务(5秒)
this.scheduledExecutorService.scheduleAtFixedRate(() -> {
    this.persistAllConsumerOffset();
}, 1000 * 10, 1000 * 5, TimeUnit.MILLISECONDS);
8.4.2 广播模式

存储在本地文件

java 复制代码
public class LocalFileOffsetStore implements OffsetStore {
    private String storePath;  // ~/.rocketmq_offsets/{clientId}/offsets.json

    @Override
    public void persistAll(Set<MessageQueue> mqs) {
        // 持久化到本地文件
        OffsetSerializeWrapper offsetSerializeWrapper =
            new OffsetSerializeWrapper();
        offsetSerializeWrapper.setOffsetTable(this.offsetTable);

        String jsonString = offsetSerializeWrapper.toJson(true);
        MixAll.string2File(jsonString, this.storePath);
    }
}

8.5 消费重试

8.5.1 重试队列

命名规则

复制代码
%RETRY% + ConsumerGroup
例如:%RETRY%ConsumerGroupTest

重试流程

复制代码
1. 消费失败
2. 发送消息到 %RETRY% Topic
3. 延迟一定时间后重新消费
4. 重试次数超过最大值(默认16次),进入死信队列
8.5.2 延迟级别
java 复制代码
// 重试次数与延迟时间的对应关系
1次: 10s
2次: 30s
3次: 1min
4次: 2min
5次: 3min
6次: 4min
7次: 5min
8次: 6min
9次: 7min
10次: 8min
11次: 9min
12次: 10min
13次: 20min
14次: 30min
15次: 1h
16次: 2h
8.5.3 死信队列

命名规则

复制代码
%DLQ% + ConsumerGroup
例如:%DLQ%ConsumerGroupTest

特点

  • 重试次数超过 16 次的消息进入死信队列
  • 死信队列的消息不会再被消费
  • 需要人工介入处理

九、高可用与高性能设计

9.1 高可用设计

9.1.1 NameServer 集群

特点

  • 无状态节点
  • 节点之间不通信
  • 任一节点挂掉不影响服务

容错机制

复制代码
Producer/Consumer 连接任一 NameServer
NameServer 挂掉后,客户端自动切换
9.1.2 Broker 主从

主从架构

复制代码
Master-Slave1-Slave2

故障转移

  • 手动切换:修改配置,重启服务
  • 自动切换:DLedger 模式(基于 Raft)

DLedger 模式

复制代码
1. 使用 Raft 协议选举 Leader
2. Master 宕机,自动选举新 Master
3. 数据强一致性
4. 性能略有下降

配置

properties 复制代码
enableDLegerCommitLog=true
dLegerGroup=broker-a
dLegerPeers=n0-127.0.0.1:40911;n1-127.0.0.1:40912;n2-127.0.0.1:40913
dLegerSelfId=n0
9.1.3 消息可靠性

生产端

复制代码
1. 同步发送
2. 失败重试(默认2次)
3. 发送确认(SendResult)

Broker 端

复制代码
1. 同步刷盘
2. 同步主从复制
3. 至少保证一个 Slave 成功

消费端

复制代码
1. 消费失败自动重试
2. 消费进度持久化
3. 重试队列 + 死信队列

9.2 高性能设计

9.2.1 顺序写入
复制代码
CommitLog 顺序写 → 600MB/s(机械硬盘)
随机写 → 100KB/s
性能提升 6000 倍
9.2.2 零拷贝
复制代码
传统方式:4次拷贝,2次系统调用
mmap:3次拷贝,1次系统调用
sendfile:2次拷贝,1次系统调用
9.2.3 PageCache
复制代码
写入:数据先写 PageCache,异步刷盘
读取:优先从 PageCache 读取
优点:减少磁盘 IO,提升性能
9.2.4 异步刷盘
复制代码
写入 PageCache → 立即返回 → 后台异步刷盘
TPS 提升 10 倍以上
9.2.5 批量操作
复制代码
批量拉取消息:32条/次
批量消费:最大 1 条/次(可配置)
批量发送:最大 1MB
9.2.6 长轮询
复制代码
减少无效轮询
降低延迟(毫秒级)
减少服务器压力
9.2.7 消息过滤
复制代码
Tag 过滤:HashCode 比对(Broker 端)
SQL 过滤:表达式计算(Broker 端)
减少网络传输
9.2.8 文件预分配
复制代码
CommitLog:预分配 1GB
ConsumeQueue:预分配 6MB
避免运行时动态扩容
9.2.9 内存锁定
复制代码
mlock:锁定内存,防止 swap
madvise:预读优化
提高访问性能
9.2.10 Netty 高性能网络
复制代码
Reactor 多线程模型
零拷贝
内存池
高效的序列化

十、使用方法与实战

10.1 环境搭建

10.1.1 下载安装
bash 复制代码
# 下载
wget https://archive.apache.org/dist/rocketmq/4.9.4/rocketmq-all-4.9.4-bin-release.zip

# 解压
unzip rocketmq-all-4.9.4-bin-release.zip
cd rocketmq-4.9.4

# 配置环境变量
export ROCKETMQ_HOME=/path/to/rocketmq-4.9.4
export PATH=$PATH:$ROCKETMQ_HOME/bin
10.1.2 启动 NameServer
bash 复制代码
# 启动
nohup sh bin/mqnamesrv &

# 查看日志
tail -f ~/logs/rocketmqlogs/namesrv.log
10.1.3 启动 Broker
bash 复制代码
# 启动
nohup sh bin/mqbroker -n localhost:9876 &

# 查看日志
tail -f ~/logs/rocketmqlogs/broker.log
10.1.4 关闭服务
bash 复制代码
sh bin/mqshutdown broker
sh bin/mqshutdown namesrv

10.2 Java 客户端

10.2.1 Maven 依赖
xml 复制代码
<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-client</artifactId>
    <version>4.9.4</version>
</dependency>
10.2.2 普通消息

生产者

java 复制代码
public class Producer {
    public static void main(String[] args) throws Exception {
        // 1. 创建生产者
        DefaultMQProducer producer = new DefaultMQProducer("ProducerGroup");
        producer.setNamesrvAddr("localhost:9876");

        // 2. 启动
        producer.start();

        // 3. 发送消息
        for (int i = 0; i < 10; i++) {
            Message msg = new Message(
                "TopicTest",           // Topic
                "TagA",                // Tag
                ("Hello RocketMQ " + i).getBytes() // Body
            );

            // 同步发送
            SendResult sendResult = producer.send(msg);
            System.out.printf("%s%n", sendResult);
        }

        // 4. 关闭
        producer.shutdown();
    }
}

消费者

java 复制代码
public class Consumer {
    public static void main(String[] args) throws Exception {
        // 1. 创建消费者
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("ConsumerGroup");
        consumer.setNamesrvAddr("localhost:9876");

        // 2. 订阅 Topic
        consumer.subscribe("TopicTest", "*");

        // 3. 注册监听器
        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;
            }
        });

        // 4. 启动
        consumer.start();

        System.out.println("Consumer Started.");
    }
}
10.2.3 顺序消息

生产者

java 复制代码
public class OrderProducer {
    public static void main(String[] args) throws Exception {
        DefaultMQProducer producer = new DefaultMQProducer("OrderProducerGroup");
        producer.setNamesrvAddr("localhost:9876");
        producer.start();

        // 订单步骤
        String[] tags = {"create", "pay", "deliver", "finish"};

        // 订单ID
        for (int orderId = 0; orderId < 10; orderId++) {
            // 每个订单的4个步骤
            for (int i = 0; i < tags.length; i++) {
                Message msg = new Message(
                    "OrderTopic",
                    tags[i],
                    ("订单" + orderId + ":" + tags[i]).getBytes()
                );

                // 按订单ID选择队列,保证同一订单的消息进入同一队列
                SendResult sendResult = producer.send(msg,
                    new MessageQueueSelector() {
                        @Override
                        public MessageQueue select(
                                List<MessageQueue> mqs,
                                Message msg,
                                Object arg) {
                            Long id = (Long) arg;
                            int index = (int) (id % mqs.size());
                            return mqs.get(index);
                        }
                    },
                    (long) orderId  // 订单ID作为选择器参数
                );

                System.out.printf("SendResult: %s, msgId: %s%n",
                    sendResult.getSendStatus(), sendResult.getMsgId());
            }
        }

        producer.shutdown();
    }
}

消费者

java 复制代码
public class OrderConsumer {
    public static void main(String[] args) throws Exception {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("OrderConsumerGroup");
        consumer.setNamesrvAddr("localhost:9876");
        consumer.subscribe("OrderTopic", "*");

        // 注册顺序消息���听器
        consumer.registerMessageListener(new MessageListenerOrderly() {
            @Override
            public ConsumeOrderlyStatus consumeMessage(
                    List<MessageExt> msgs,
                    ConsumeOrderlyContext context) {
                for (MessageExt msg : msgs) {
                    System.out.printf("线程:%s, 队列:%d, 消息:%s%n",
                        Thread.currentThread().getName(),
                        msg.getQueueId(),
                        new String(msg.getBody()));
                }
                return ConsumeOrderlyStatus.SUCCESS;
            }
        });

        consumer.start();
        System.out.println("OrderConsumer Started.");
    }
}
10.2.4 延迟消息
java 复制代码
public class ScheduledProducer {
    public static void main(String[] args) throws Exception {
        DefaultMQProducer producer = new DefaultMQProducer("ScheduledProducerGroup");
        producer.setNamesrvAddr("localhost:9876");
        producer.start();

        Message msg = new Message(
            "ScheduledTopic",
            "TagA",
            "延迟消息".getBytes()
        );

        // 设置延迟级别(3 = 10s)
        // 1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
        msg.setDelayTimeLevel(3);

        SendResult sendResult = producer.send(msg);
        System.out.printf("发送时间:%s, 消息:%s%n",
            new SimpleDateFormat("HH:mm:ss").format(new Date()),
            sendResult.getMsgId());

        producer.shutdown();
    }
}
10.2.5 批量消息
java 复制代码
public class BatchProducer {
    public static void main(String[] args) throws Exception {
        DefaultMQProducer producer = new DefaultMQProducer("BatchProducerGroup");
        producer.setNamesrvAddr("localhost:9876");
        producer.start();

        // 创建消息列表
        List<Message> messages = new ArrayList<>();
        messages.add(new Message("BatchTopic", "TagA", "消息1".getBytes()));
        messages.add(new Message("BatchTopic", "TagA", "消息2".getBytes()));
        messages.add(new Message("BatchTopic", "TagA", "消息3".getBytes()));

        // 批量发送
        SendResult sendResult = producer.send(messages);
        System.out.printf("批量发送结果: %s%n", sendResult);

        producer.shutdown();
    }
}

批量消息分割器(单批次不超过 1MB):

java 复制代码
public class ListSplitter implements Iterator<List<Message>> {
    private final int SIZE_LIMIT = 1024 * 1024; // 1MB
    private final List<Message> messages;
    private int currIndex;

    public ListSplitter(List<Message> messages) {
        this.messages = messages;
    }

    @Override
    public boolean hasNext() {
        return currIndex < messages.size();
    }

    @Override
    public List<Message> next() {
        int startIndex = currIndex;
        int totalSize = 0;

        for (; currIndex < messages.size(); currIndex++) {
            Message message = messages.get(currIndex);
            int tmpSize = message.getTopic().length() + message.getBody().length;

            if (tmpSize > SIZE_LIMIT) {
                if (currIndex == startIndex) {
                    currIndex++;
                }
                break;
            }

            if (tmpSize + totalSize > SIZE_LIMIT) {
                break;
            }

            totalSize += tmpSize;
        }

        return messages.subList(startIndex, currIndex);
    }
}

// 使用
ListSplitter splitter = new ListSplitter(messages);
while (splitter.hasNext()) {
    List<Message> batch = splitter.next();
    producer.send(batch);
}
10.2.6 事务消息
java 复制代码
public class TransactionProducer {
    public static void main(String[] args) throws Exception {
        // 1. 创建事务监听器
        TransactionListener transactionListener = new TransactionListener() {
            @Override
            public LocalTransactionState executeLocalTransaction(
                    Message msg, Object arg) {
                // 执行本地事务
                String tags = msg.getTags();

                if ("TagA".equals(tags)) {
                    // 执行成功,提交事务
                    return LocalTransactionState.COMMIT_MESSAGE;
                } else if ("TagB".equals(tags)) {
                    // 执行失败,回滚事务
                    return LocalTransactionState.ROLLBACK_MESSAGE;
                } else {
                    // 未知状态,等待回查
                    return LocalTransactionState.UNKNOW;
                }
            }

            @Override
            public LocalTransactionState checkLocalTransaction(
                    MessageExt msg) {
                // 回查本地事务状态
                String tags = msg.getTags();

                // 根据业务逻辑查询事务状态
                // 这里简单返回 COMMIT
                return LocalTransactionState.COMMIT_MESSAGE;
            }
        };

        // 2. 创建事务生产者
        TransactionMQProducer producer = new TransactionMQProducer("TransactionProducerGroup");
        producer.setNamesrvAddr("localhost:9876");

        // 3. 设置事务监听器
        producer.setTransactionListener(transactionListener);

        // 4. 设置线程池(回查线程池)
        ExecutorService executorService = new ThreadPoolExecutor(
            2, 5, 100, TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(2000),
            r -> {
                Thread thread = new Thread(r);
                thread.setName("client-transaction-msg-check-thread");
                return thread;
            }
        );
        producer.setExecutorService(executorService);

        // 5. 启动
        producer.start();

        // 6. 发送事务消息
        String[] tags = {"TagA", "TagB", "TagC"};
        for (int i = 0; i < 3; i++) {
            Message msg = new Message(
                "TransactionTopic",
                tags[i % tags.length],
                ("事务消息 " + i).getBytes()
            );

            TransactionSendResult sendResult =
                producer.sendMessageInTransaction(msg, null);

            System.out.printf("发送结果: %s, 本地事务状态: %s%n",
                sendResult.getSendStatus(),
                sendResult.getLocalTransactionState());
        }

        Thread.sleep(Integer.MAX_VALUE);
        producer.shutdown();
    }
}
10.2.7 消息过滤

Tag 过滤

java 复制代码
// 生产者
Message msg = new Message("FilterTopic", "TagA", "消息内容".getBytes());

// 消费者
consumer.subscribe("FilterTopic", "TagA || TagB");

SQL 过滤

java 复制代码
// 生产者
Message msg = new Message("FilterTopic", "TagA", "消息内容".getBytes());
msg.putUserProperty("age", "18");
msg.putUserProperty("vip", "true");

// 消费者(需要开启 enablePropertyFilter=true)
consumer.subscribe("FilterTopic",
    MessageSelector.bySql("age > 16 AND vip = 'true'"));

10.3 Spring Boot 集成

10.3.1 依赖
xml 复制代码
<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-spring-boot-starter</artifactId>
    <version>2.2.2</version>
</dependency>
10.3.2 配置
yaml 复制代码
rocketmq:
  name-server: localhost:9876
  producer:
    group: spring-boot-producer-group
    send-message-timeout: 3000
    retry-times-when-send-failed: 2
  consumer:
    group: spring-boot-consumer-group
    consume-thread-min: 20
    consume-thread-max: 64
10.3.3 生产者
java 复制代码
@Service
public class ProducerService {

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    public void sendMessage(String topic, String message) {
        // 同步发送
        SendResult sendResult = rocketMQTemplate.syncSend(topic, message);
        System.out.println(sendResult);
    }

    public void sendMessageAsync(String topic, String message) {
        // 异步发送
        rocketMQTemplate.asyncSend(topic, message, new SendCallback() {
            @Override
            public void onSuccess(SendResult sendResult) {
                System.out.println("发送成功:" + sendResult);
            }

            @Override
            public void onException(Throwable e) {
                System.out.println("发送失败:" + e.getMessage());
            }
        });
    }

    public void sendOrderlyMessage(String topic, String message, String hashKey) {
        // 顺序发送
        rocketMQTemplate.syncSendOrderly(topic, message, hashKey);
    }

    public void sendDelayMessage(String topic, String message, int delayLevel) {
        // 延迟发送
        rocketMQTemplate.syncSend(topic,
            MessageBuilder.withPayload(message).build(),
            3000, delayLevel);
    }
}
10.3.4 消费者
java 复制代码
@Service
@RocketMQMessageListener(
    topic = "TestTopic",
    consumerGroup = "spring-boot-consumer-group",
    selectorExpression = "*"  // Tag 过滤
)
public class ConsumerService implements RocketMQListener<String> {

    @Override
    public void onMessage(String message) {
        System.out.println("收到消息:" + message);
    }
}

顺序消费

java 复制代码
@Service
@RocketMQMessageListener(
    topic = "OrderTopic",
    consumerGroup = "order-consumer-group",
    consumeMode = ConsumeMode.ORDERLY  // 顺序消费
)
public class OrderConsumerService implements RocketMQListener<String> {

    @Override
    public void onMessage(String message) {
        System.out.println("顺序消费:" + message);
    }
}

十一、最佳实践

11.1 生产者最佳实践

11.1.1 合理设置超时时间
java 复制代码
producer.setSendMsgTimeout(3000);  // 3秒
11.1.2 合理设置重试次数
java 复制代码
// 同步发送失败重试次数
producer.setRetryTimesWhenSendFailed(2);

// 异步发送失败重试次数
producer.setRetryTimesWhenSendAsyncFailed(2);
11.1.3 使用 Keys 标识消息
java 复制代码
Message msg = new Message("Topic", "Tag", "Body".getBytes());
msg.setKeys("OrderId_123456");  // 便于后续查询
11.1.4 合理使用批量发送
java 复制代码
// 适用场景:日志收集、数据同步
// 注意:单批次不超过 1MB
List<Message> messages = new ArrayList<>();
// ... 添加消息
producer.send(messages);
11.1.5 生产者实例复用
java 复制代码
// 一个应用创建一个 Producer 实例即可
// 多线程共享
private static DefaultMQProducer producer;

static {
    producer = new DefaultMQProducer("ProducerGroup");
    producer.start();
}

11.2 消费者最佳实践

11.2.1 幂等性处理
java 复制代码
@Override
public ConsumeConcurrentlyStatus consumeMessage(
        List<MessageExt> msgs,
        ConsumeConcurrentlyContext context) {
    for (MessageExt msg : msgs) {
        String msgId = msg.getMsgId();

        // 检查是否已处理(Redis/DB)
        if (isProcessed(msgId)) {
            continue;
        }

        // 业务处理
        process(msg);

        // 标记已处理
        markProcessed(msgId);
    }
    return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
11.2.2 消费失败处理
java 复制代码
@Override
public ConsumeConcurrentlyStatus consumeMessage(
        List<MessageExt> msgs,
        ConsumeConcurrentlyContext context) {
    try {
        // 业务处理
        process(msgs);
        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;

    } catch (Exception e) {
        // 记录日志
        log.error("消费失败", e);

        // 消费失败,稍后重试
        return ConsumeConcurrentlyStatus.RECONSUME_LATER;
    }
}
11.2.3 消费线程数配置
java 复制代码
// 消费线程数 = CPU核心数
consumer.setConsumeThreadMin(20);
consumer.setConsumeThreadMax(64);
11.2.4 批量消费配置
java 复制代码
// 每次拉取消息数量
consumer.setPullBatchSize(32);

// 每次消费消息数量
consumer.setConsumeMessageBatchMaxSize(1);
11.2.5 消费超时设置
java 复制代码
// 消费超时时间(分钟)
consumer.setConsumeTimeout(15);

11.3 Topic 设计最佳实践

11.3.1 Topic 命名规范
复制代码
业务域_业务_动作
例如:order_order_create
     user_user_register
     pay_payment_success
11.3.2 合理划分 Topic
复制代码
按业务领域划分:
- 订单 Topic
- 用户 Topic
- 支付 Topic

不要所有消息都用一个 Topic
11.3.3 Queue 数量配置
properties 复制代码
# 队列数量 = 消费者数量
# 队列数量建议 4-8 个
defaultTopicQueueNums=4

11.4 性能优化最佳实践

11.4.1 异步刷盘 + 异步复制
properties 复制代码
# 高性能场景
flushDiskType=ASYNC_FLUSH
brokerRole=ASYNC_MASTER
11.4.2 同步刷盘 + 同步复制
properties 复制代码
# 高可靠场景
flushDiskType=SYNC_FLUSH
brokerRole=SYNC_MASTER
11.4.3 PageCache 预热
properties 复制代码
# 开启文件预热
warmMapedFileEnable=true
11.4.4 关闭消息轨迹
java 复制代码
// 消息轨迹会影响性能
DefaultMQProducer producer = new DefaultMQProducer(
    "ProducerGroup",
    null,  // RPCHook
    false  // enableMsgTrace
);

11.5 运维最佳实践

11.5.1 监控指标
复制代码
生产端:
- 发送 TPS
- 发送耗时
- 发送成功率

消费端:
- 消费 TPS
- 消费耗时
- 消费堆积量
- 消费成功率

Broker:
- 磁盘使用率
- 消息堆积量
- PageCache 命中率
- GC 情况
11.5.2 告警配置
复制代码
- 消息堆积超过 10W 条
- 消费延迟超过 5 分钟
- 磁盘使用率超过 75%
- Broker 宕机
- NameServer 宕机
11.5.3 容量规划
复制代码
磁盘容量 = 每天消息量 × 消息大小 × 保留天数 × 1.5(冗余)

例如:
每天 1 亿条消息
平均消息大小 1KB
保留 3 天
磁盘容量 = 100000000 × 1KB × 3 × 1.5 = 450GB
相关推荐
huisheng_qaq1 天前
【RocketMq源码篇-03】dashboard安装搭建和启动详解(集群版)
rocketmq·rocketmq集群·rocketmq源码·dashboard可视化界面·mq中间件
雨中飘荡的记忆2 天前
布式事务详解:从理论到实践(RocketMQ + Seata)
java·rocketmq
galaxyffang7 天前
RocketMQ 为什么性能不如 Kafka?
分布式·kafka·rocketmq
一叶飘零_sweeeet7 天前
从 Kafka 到 RocketMQ:迁移实战全攻略
分布式·kafka·rocketmq
一叶飘零_sweeeet10 天前
Spring Cloud Alibaba RocketMQ 实战:从底层原理到微服务落地全攻略
微服务·架构·rocketmq
一叶飘零_sweeeet11 天前
RocketMQ 核心解密:NameServer 路由发现与负载均衡的底层逻辑全解析
负载均衡·rocketmq
无心水11 天前
【分布式利器:事务】5、本地消息表vs事务消息:异步方案怎么选?
分布式·rocketmq·分布式事务·saga·事务消息·分布式利器·2pc3pc
huisheng_qaq11 天前
【RocketMq源码篇-02】rocketmq集群搭建详细过程(docker版-2主2从)
docker·rocketmq·rocketmq集群·rocketmq源码·2主2从
无心水12 天前
【分布式利器:RocketMQ】RocketMQ基本原理详解:架构、流程与核心特性(附实战场景)
中间件·架构·rocketmq·topic·rocketmq基本原理·电商金融mq·nameserver