一、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。
发送方式:
- 同步发送:等待 Broker 响应,可靠性高
- 异步发送:不等待响应,高吞吐量
- 单向发送:只管发送,不关心结果,性能最高
负载均衡:
- 轮询选择 MessageQueue
- 支持自定义队列选择策略
2.1.4 Consumer(消费者)
作用:消息消费者,从 Broker 拉取消息进行消费。
消费模式:
- 集群模式(Clustering):消费者组内负载均衡消费
- 广播模式(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 启动流程
-
NameServer 启动
- 监听端口(默认 9876)
- 初始化路由信息表
-
Broker 启动
- 加载配置文件
- 启动各个服务模块
- 向所有 NameServer 注册
- 每 30s 发送心跳保持注册信息
-
Producer 启动
- 连接 NameServer
- 获取 Topic 路由信息
- 建立与 Broker 的连接
-
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 过期清理
触发条件(满足任一):
- 默认凌晨 4 点自动删除
- 磁盘空间不足(超过 75%)
- 手动触发清理
保留时间:
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