源码深入篇:RocketMQ 核心源码精读指南

系列第六阶段:源码深入篇

你好,又见面了。

这是 RocketMQ 系列的最后一篇,也是最硬核的一篇——源码阅读与分析

经过前五个阶段的学习,你已经掌握了 RocketMQ 的架构原理、存储机制、发送消费流程、进阶特性和部署运维。可以说,你已经是一名合格的 RocketMQ 开发者了。

但“合格”和“精通”之间,还隔着源码这道门槛。

为什么建议你读源码?三个原因:

  1. 遇到诡异问题时,源码是你最可靠的“字典”——它能告诉你框架到底是怎么运作的
  2. 性能调优时,只有理解了底层实现,才知道参数该怎么调
  3. 面试时,能讲清楚源码的实现细节,是区分“会用”和“真懂”的分水岭

今天这篇文章,我会带你走一遍 RocketMQ 源码的“地图”——从工程结构到各个核心模块,告诉你从哪里入手、看什么、怎么看。老规矩,配合流程图和代码片段,一步一图。

十六、源码阅读与分析

源码工程结构与模块划分

在开始阅读源码之前,我们先要搞清楚 RocketMQ 源码工程的整体布局。以 RocketMQ 5.x 主干分支为例,源码目录结构如下:

rocketmq/
├── broker/          # Broker 服务端核心模块
├── client/          # 客户端实现(Producer、Consumer)
├── common/          # 公共工具包(常量、配置、工具类)
├── distribution/    # 发行包与安装配置脚本
├── example/         # 示例代码
├── filtersrv/       # 消息过滤服务器(已废弃)
├── logging/         # 日志组件
├── namesrv/         # NameServer 路由中心
├── openmessaging/   # OpenMessaging 标准兼容
├── proxy/           # 5.x 新增:代理服务(gRPC/HTTP)
├── remoting/        # 远程通信模块(基于 Netty)
├── store/           # 消息存储底层实现
├── test/            # 单元测试与集成测试
└── tools/           # 运维管理命令行工具

各模块职责速览

模块 职责
namesrv/ NameServer 路由中心,实现 Broker 注册、路由管理、心跳检测
broker/ Broker 服务端核心,实现消息的接收、存储、转发、投递、消费进度管理
store/ 消息存储底层,CommitLog、ConsumeQueue、IndexFile 等
remoting/ 远程通信,基于 Netty 实现客户端与服务端的网络通信
client/ 客户端 API,Producer 和 Consumer 的核心逻辑
proxy/ 5.x 新增:代理服务,支持 gRPC 协议客户端收发消息
controller/ 5.x 新增:控制器,帮助 Broker 做主从切换

💡 阅读建议:如果你第一次读 RocketMQ 源码,建议按这个顺序入手:remoting(通信基础)→ namesrv(路由)→ store(存储)→ broker(服务端)→ client(客户端)。由浅入深,逐步推进。

NameServer 核心源码剖析(路由管理)

NameServer 是 RocketMQ 的“轻量级注册中心”。它的源码非常精简——只有八个类,不到 1000 行代码

核心类:RouteInfoManager

NameServer 的路由管理核心在 org.apache.rocketmq.namesrv.routeinfo.RouteInfoManager 中实现。它通过 5 个核心数据结构来维护路由元信息:

// 路由元信息的 5 个核心数据结构
private final HashMap<String/* topic */, List<QueueData>> topicQueueTable;
private final HashMap<String/* brokerName */, BrokerData> brokerAddrTable;
private final HashMap<String/* clusterName */, Set<String/* brokerName */>> clusterAddrTable;
private final HashMap<String/* brokerAddr */, BrokerLiveInfo> brokerLiveTable;
private final HashMap<String/* brokerAddr */, List<String>/* Filter Server */> filterServerTable;
数据结构 作用
topicQueueTable Topic → Queue 列表的映射
brokerAddrTable Broker 名称 → Broker 地址信息的映射
clusterAddrTable 集群名称 → Broker 名称集合的映射
brokerLiveTable Broker 地址 → 存活信息的映射(含心跳时间)
filterServerTable Broker 地址 → 过滤服务器列表的映射

Broker 心跳注册流程

Broker 启动后,每隔 30 秒向所有 NameServer 发送心跳命令。源码中使用 CountDownLatch 实现多线程同步,并发地向所有 NameServer 发送注册请求:

// Broker 向所有 NameServer 发送心跳(源码简化)
for (final String namesrvAddr : nameServerAddressList) {
    brokerOuterExecutor.execute(() -> {
        RegisterBrokerResult result = registerBroker(namesrvAddr, ...);
        // 处理注册结果
    });
}
countDownLatch.await(timeoutMills, TimeUnit.MILLISECONDS);

NameServer 之间无状态、不通信

NameServer 集群节点之间没有任何数据同步和通信。每个节点独立维护路由信息,即使某个时刻各节点的数据不完全一致,也不会影响消息的发送。这种设计极大简化了 NameServer 的实现,也让它变得极为轻量和稳定。

Broker 核心源码剖析(消息存储、转发)

Broker 是 RocketMQ 最复杂的模块,涉及消息的接收、存储、转发、投递和消费进度管理。

Broker 的分层设计

flowchart TB subgraph Layers[Broker 分层架构] A[请求处理层<br>SendMessageProcessor / PullMessageProcessor<br>解析 RemotingCommand 的 RequestCode] B[业务逻辑层<br>DefaultMessageStore<br>putMessage / getMessage] C[文件映射层<br>MappedFile<br>基于 MappedByteBuffer] D[存储层<br>CommitLog / ConsumeQueue / IndexFile] end A --> B --> C --> D

Broker 启动流程

  1. 加载持久化的配置信息(消费进度、订阅信息等)
  2. 加载 DefaultMessageStore(消息存储组件),创建 MappedFileQueue 映射 CommitLog、ConsumeQueue、IndexFile 等文件
  3. 创建并启动 BrokerController 控制器,处理消息的发送和接收

核心存储设计理念

RocketMQ 将所有主题的消息不分主题一律顺序写入 CommitLog 文件。这与 Kafka 按分区存储的设计不同——Kafka 在 Topic 和分区数量增长时,写入性能会下降,而 RocketMQ 的表现则稳定得多。因此,Kafka 适合 Topic 和分区较少的场景,RocketMQ 更适合多 Topic、多消费端的业务场景

消息发送流程源码剖析

消息发送的入口是 DefaultMQProducer,其核心实现在 DefaultMQProducerImpl 中。

发送流程的四个核心步骤

image

Producer 启动流程

  1. 检测配置:判断生产者组是否合法
  2. 创建客户端实例MQClientInstance 通过 MQClientManager 单例创建,是非常核心的类,每个实例有唯一的 clientId
  3. 注册本地生产者:将 Producer 注册到 MQClientInstanceproducerTable
  4. 启动客户端实例:启动 Netty 通信模块、定时任务、负载均衡服务

定时任务是 Producer 的核心机制之一:

  • 发送心跳:每隔 30 秒将客户端信息发送到 Broker
  • 更新路由:定时从 NameServer 拉取最新的 Topic 路由信息

发送消息的核心方法sendDefaultImpl

  1. 获取主题发布信息(topicPublishInfo
  2. 根据路由算法选择一个消息队列(selectOneMessageQueue
  3. 调用 sendKernelImpl 发送消息,封装成 SendResult

消息拉取与消费流程源码剖析

RocketMQ 的消费者有 DefaultMQPushConsumerDefaultMQPullConsumer 两种,但底层都是基于长轮询实现的

Push 消费者启动流程DefaultMQPushConsumerImpl#start):

flowchart TB Start[consumer.start] --> Step1[加载偏移量<br>广播模式存本地 / 集群模式存 Broker] Step1 --> Step2[启动 Netty 客户端<br>与 Broker 建立通信连接] Step2 --> Step3[启动定时任务<br>发送心跳、更新路由] Step3 --> Step4[启动 PullMessageService<br>异步拉取消息] Step4 --> Step5[启动 RebalanceService<br>负载均衡] Step5 --> End[Consumer 就绪]

关键点:消费者启动时需要加载各个 Topic 的偏移量。广播模式下偏移量存储在消费者本地,集群模式下存储在 Broker 端。

拉取消息的核心流程

  1. PullMessageService 线程不断从 pullRequestQueue 中取出 PullRequest
  2. 向 Broker 发起拉取请求(包含 ConsumerGroup、Topic、Queue、queueOffset 等信息)
  3. Broker 通过长轮询机制响应(有消息立即返回,无消息挂起等待)
  4. 拉取到的消息提交到消费线程池处理

Push 与 Pull 的本质:Push 模式只是在客户端将消息拉取到本地后,自动回调业务方的监听器执行消费逻辑。内核依然是 Pull + 长轮询。

CommitLog 写入流程源码剖析

CommitLog 是 RocketMQ 存储的核心,所有消息都顺序写入 CommitLog。

CommitLog 的核心数据结构

组件 说明
MappedFile 单个文件的内存映射,基于 MappedByteBuffer
MappedFileQueue 一组 MappedFile 的队列,管理文件的滚动
CommitLog 消息写入的入口,封装了写入逻辑

每个 CommitLog 文件默认 1GB,文件名以起始偏移量命名(如 00000000000000000000)。

消息写入流程

flowchart TB Start[Producer 发送消息] --> Step1[Broker 接收请求<br>SendMessageProcessor.processRequest] Step1 --> Step2[DefaultMessageStore.putMessage<br>消息存储入口] Step2 --> Step3[CommitLog.putMessage<br>执行写入] Step3 --> Step4[获取写入锁<br>可重入锁或自旋锁] Step4 --> Step5[通过 MappedFile<br>将消息追加到 PageCache] Step5 --> Step6[更新写入指针<br>释放锁] Step6 --> Step7{刷盘策略} Step7 -->|同步刷盘| Step8[MappedByteBuffer.force<br>等待刷盘完成] Step7 -->|异步刷盘| Step9[唤醒刷盘线程<br>立即返回] Step8 --> End[返回写入结果] Step9 --> End

锁机制putMessage 会有多个线程并行处理,需要加锁。可以通过配置选择使用可重入锁还是自旋锁useReentrantLockWhenPutMessage)。

刷盘的最终实现都是使用 NIO 中的 MappedByteBuffer.force() 将映射区的数据写入磁盘。

ConsumeQueue 构建流程源码剖析(ReputMessageService)

ConsumeQueue 是 CommitLog 的索引文件,由 ReputMessageService 这个后台线程负责构建。

ReputMessageService 的核心机制

  • Broker 启动时会开启一个线程,每毫秒执行一次 doReput() 方法
  • 它有一个属性 reputFromOffset,记录消息重放的偏移量
  • 每次执行时,从 reputFromOffset 开始读取 CommitLog 中的消息
  • 逐条解析消息,构建 ConsumeQueue 和 IndexFile

ConsumeQueue 条目格式

每个 ConsumeQueue 条目固定 20 个字节

字段 长度 说明
CommitLog 偏移量 8 字节 消息在 CommitLog 中的物理位置
消息长度 4 字节 消息的字节数
Tag 哈希码 8 字节 Tag 的哈希值,用于消息过滤

工作流程图

flowchart TB Start[Broker 启动] --> Init[启动 ReputMessageService 线程] Init --> Loop[每 1ms 执行一次 doReput] Loop --> Check[从 reputFromOffset<br>检查 CommitLog 是否有新数据] Check -->|有新数据| Parse[逐条解析消息] Parse --> BuildCQ[构建 ConsumeQueue 条目<br>offset + size + tagHash] BuildCQ --> BuildIndex[构建 IndexFile<br>Key → Offset 映射] BuildIndex --> Update[更新 reputFromOffset] Check -->|无新数据| Sleep[短暂休眠] Update --> Loop Sleep --> Loop

Rebalance 机制源码剖析

Rebalance(重平衡)是消费者组内 Queue 分配的动态调整机制。

触发入口RebalanceService 是一个线程任务,在消费者客户端启动时被调用。

flowchart TB Start[RebalanceService.run] --> Do[doRebalance] Do --> Iterate[遍历 consumerTable<br>获取每个 MQConsumerInner] Iterate --> RebalanceByTopic[rebalanceByTopic<br>执行重平衡] RebalanceByTopic --> CheckMode{消费模式} CheckMode -->|广播模式| Broadcast[简化版分配<br>所有 Queue 分配给所有 Consumer] CheckMode -->|集群模式| Cluster[核心分配逻辑<br>根据分配策略计算] Cluster --> Allocate[AllocateMessageQueueStrategy<br>平均分配 / 环形分配] Allocate --> Update[更新 ProcessQueue<br>创建/释放 PullRequest] Update --> End[完成]

核心实现:负载均衡的最终执行逻辑在 rebalanceByTopic() 方法中。广播模式和集群模式的实现不同,集群模式是核心实现。

分配策略

策略 实现类 特点
平均分配 AllocateMessageQueueAveragely 尽可能均匀分配,默认策略
环形分配 AllocateMessageQueueAveragelyByCircle 轮流分配,类似发牌

事务消息源码剖析(半消息与回查)

事务消息是 RocketMQ 最复杂的特性之一,其核心是半消息(Half Message)事务回查(Transaction Check) 机制。

半消息的存储

半消息发送成功后,会进入 RocketMQ 内部的 RMQ_SYS_TRANS_HALF_TOPIC 的 ConsumeQueue 中。这个消息只存储在 CommitLog 中,但在 ConsumeQueue 中不可见,因此消费者无法消费到它。

事务消息的完整流程

sequenceDiagram participant P as Producer participant B as Broker participant App as 业务应用 P->>B: 1. 发送半消息 B->>B: 2. 写入 RMQ_SYS_TRANS_HALF_TOPIC<br>(CommitLog 可见,ConsumeQueue 不可见) B-->>P: 3. 半消息发送成功 P->>App: 4. 执行本地事务 App-->>P: 5. 返回事务状态 alt COMMIT P->>B: 6a. 提交事务 B->>B: 7a. 将消息转发到目标 Topic else ROLLBACK P->>B: 6b. 回滚事务 B->>B: 7b. 删除半消息 else UNKNOWN Note over P,B: 进入回查流程 loop 每 60 秒回查 B->>P: 8c. 发起回查请求 P->>App: 9c. 检查本地事务状态 App-->>P: 10c. 返回状态 P->>B: 11c. 提交最终事务状态 end end

事务回查机制

如果 Producer 在发送半消息后未能及时通知 Broker 提交或回滚,Broker 会定期(默认 60 秒)回查 Producer 的事务状态。check 方法会对半消息进行过滤(如超过 72 小时的事务消息算作过期),只保留符合条件的半消息进行回查。

消息重试与死信源码剖析

消息重试机制

  • RocketMQ 的消费者在消息处理失败时,会根据返回状态码自动触发重试
  • 默认情况下,消息最多重试 16 次(并发消费)
  • 每次重试的时间间隔呈指数增长(如 10s → 30s → 1min → 2min…)

重试与死信的流转

flowchart TB Start[消息消费] --> Check{消费成功?} Check -->|是| Done[提交 Offset] Check -->|否| Retry[进入重试队列<br>%RETRY%ConsumerGroup] Retry --> Delay[延迟重试<br>时间逐次递增] Delay --> Count{重试次数<br>≤ 16 次?} Count -->|是| Start Count -->|否| DLQ[进入死信队列<br>%DLQ%ConsumerGroup] DLQ --> Manual[需要人工介入处理]

并发消费与顺序消费的重试差异

  • 并发消费:失败的消息进入重试队列,不影响其他消息的消费
  • 顺序消费:一条消息失败会阻塞该 Queue 的后续消息,直到重试成功或进入 DLQ

死信队列

当消息重试次数超过最大重试次数(默认 16 次),消息会被放入死信主题%DLQ%ConsumerGroup)。死信队列中的消息需要人工介入处理

Netty 通信层源码剖析

RocketMQ 的底层通信完全基于 Netty 实现

整体架构

  • Broker 端:Netty 服务器,负责与客户端的连接请求处理
  • Producer/Consumer 端:Netty 客户端,负责与 Broker 的通信及请求响应处理

Netty 多线程模型

RocketMQ 在 Netty 基础之上采用了多线程分离设计,将 I/O 线程和业务处理线程分开。

核心类

职责
NettyRemotingServer 服务端实现,底层基于 ServerBootstrap
NettyRemotingClient 客户端实现
NettyServerConfig / NettyClientConfig 通信配置

连接感知

Broker 通过 Netty 的 ChannelInboundHandlerAdapter#channelInactive() 可以实时感知到 Consumer/Producer 的下线。这为 Rebalance 和故障剔除提供了基础。

消息过滤源码剖析

RocketMQ 支持两种消息过滤方式:Tag 过滤SQL92 过滤

Tag 过滤

  • 根据消息的 Tag 进行过滤
  • 性能极高,在 ConsumeQueue 中存储了 Tag 的哈希码(8 字节),过滤时只需比对哈希值
  • 一条消息只能有一个 Tag,这是它的主要限制

SQL92 过滤

  • 使用 SQL92 语法作为过滤规则表达式
  • 可以过滤消息的属性和 Tag(在 SQL 语法中,Tag 的属性名称为 TAGS
  • 比 Tag 过滤更灵活,但性能开销更大
  • 需要设置 Broker 配置项 enablePropertyFilter=true(默认为 false)

两种过滤方式的对比

对比维度 Tag 过滤 SQL92 过滤
过滤依据 Tag 字符串 用户自定义属性 + Tag
性能 极高(哈希比对) 较低(解析 SQL + 遍历属性)
灵活性 低(只能一个 Tag) 高(复杂条件组合)
Broker 配置 默认开启 enablePropertyFilter=true

过滤表达式类型在源码中定义为 ExpressionType.TAGExpressionType.SQL92。SQL92 表达式需要先编译检查合法性,再使用编译后的表达式进行计算。

源码阅读实战建议

读完上面这些模块的源码剖析,你可能跃跃欲试了。这里给你几个实战建议:

1. 搭建源码调试环境

  • 从 GitHub 克隆 RocketMQ 源码
  • 用 IDEA 导入 Maven 项目
  • 先启动 NamesrvStartup,再启动 BrokerStartup
  • 运行 example 模块中的示例代码进行调试

2. 阅读顺序建议

阶段 模块 目的
第一阶段 remoting 理解网络通信基础
第二阶段 namesrv 理解路由注册与发现
第三阶段 store 理解存储核心(CommitLog + ConsumeQueue)
第四阶段 broker 理解服务端业务逻辑
第五阶段 client 理解生产者和消费者

3. 调试断点建议

  • Producer 发送:DefaultMQProducerImpl#sendDefaultImpl
  • Consumer 拉取:PullMessageService#run
  • Broker 写入:CommitLog#putMessage
  • Broker 拉取:PullMessageProcessor#processRequest
  • Rebalance:RebalanceService#doRebalance

4. 善用日志

RocketMQ 的日志非常详细,在 ~/logs/rocketmqlogs/ 目录下:

  • broker.log:Broker 运行日志
  • namesrv.log:NameServer 日志
  • store.log:存储相关日志
  • rocketmq_client.log:客户端日志

小结

这篇文章我们完整走了一遍 RocketMQ 源码的“地图”,通过 8 张流程图 + 代码片段,搞清楚了:

  • 源码工程结构:各模块的职责划分,从哪里入手
  • NameServer:路由管理的 5 个核心数据结构、心跳注册流程
  • Broker:分层架构、启动流程、存储设计理念
  • 消息发送:4 个核心步骤、Producer 启动流程、定时任务机制
  • 消息拉取与消费:Push 消费者启动、长轮询的本质
  • CommitLog 写入:MappedFile 机制、锁策略、刷盘实现
  • ConsumeQueue 构建:ReputMessageService 的“消息重放”机制
  • Rebalance:触发入口、分配策略、广播与集群模式的区别
  • 事务消息:半消息存储、回查机制的完整流程
  • 消息重试与死信:16 次重试、指数退避、DLQ 处理
  • Netty 通信层:多线程模型、连接感知
  • 消息过滤:Tag 与 SQL92 的原理与对比

恭喜你! 从入门认知到架构原理,从存储机制到发送消费,从进阶特性到部署运维,再到今天的源码深入——你已经完整走过了 RocketMQ 学习的全过程。你现在已经是一名真正意义上的 RocketMQ 专家了。

源码阅读是一个长期的过程,不要指望一次性全部读懂。建议你带着问题去读——遇到生产环境的故障时,顺着调用栈去追源码;想优化性能时,去读相关模块的实现。带着目的读源码,事半功倍。

祝你在 RocketMQ 的进阶之路上越走越远!


系列文章:

  1. 入门认知篇 ✅
  2. 核心概念与架构篇 ✅
  3. 存储与原理篇(上)✅
  4. 存储与原理篇(中)✅
  5. 存储与原理篇(下)✅
  6. 事务消息 ✅
  7. 进阶应用篇 ✅
  8. 部署与运维篇 ✅
  9. 源码深入篇 ✅(本文)
  10. 整合实战篇 (待续...)
posted @ 2026-07-21 09:02  佛祖让我来巡山  阅读(101)  评论(0)    收藏  举报

佛祖让我来巡山博客站 - 创建于 2018-08-15

开发工程师个人站,内容主要是网站开发方面的技术文章,大部分来自学习或工作,部分来源于网络,希望对大家有所帮助。

Bootstrap中文网