普通视图

发现新文章,点击刷新页面。
今天 — 2025年12月30日首页

RocketMQ高性能揭秘:承载万亿级流量的架构奥秘|得物技术

作者 得物技术
2025年12月30日 16:42

一、前言

在分布式系统架构中,消息队列如同畅通的“信息神经网络”,承担着解耦、削峰与异步通信的核心使命。在众多成熟方案中,RocketMQ凭借其阿里巴巴与Apache双重基因,以卓越的金融级可靠性、万亿级消息堆积能力和灵活的分布式特性脱颖而出,成为构建高可用、高性能数据流转枢纽的关键技术选型。本文将深入解析RocketMQ的核心架构、设计哲学与实践要义。

二、RocketMQ架构总览

官网图片

RocketMQ架构上主要分为四部分,如上图所示: 

RocketMQ作为一款高性能、高可用的分布式消息中间件,其核心架构采用了经典的四组件协同设计,实现了消息生产、存储、路由与消费的全链路解耦与高效协同。四大组件——生产者(Producer)、消费者(Consumer)、路由中心(NameServer)和代理服务器(Broker)——各司其职,共同构建了其坚实的基石。

生产者(Producer) 作为消息的源头,负责将业务消息高效、可靠地发布到系统中。它支持分布式集群部署,并通过内置的智能负载均衡机制,自动选择最优的Broker节点与队列进行投递。

消费者(Consumer) 是消息的处理终端,同样以集群化方式工作,支持推送(Push)和拉取(Pull)两种消息获取模式。它提供了集群消费与广播消费两种模式,并能动态维护其订阅关系。

路由中心(NameServer) 是整个架构的“注册中心”,扮演着轻量级服务发现的角色。所有Broker节点都会向NameServer注册,并通过定期心跳汇报健康状态。生产者与消费者则从NameServer获取实时的主题路由与Broker信息,从而实现消息寻址的完全解耦。

代理服务器(Broker) 是消息存储与流转的核心,负责消息的持久化存储、投递与查询。为了保障高可用性,Broker通常采用主从(Master-Slave)部署架构,确保数据与服务在故障时能无缝切换。其内部集成了通信处理、存储引擎、索引服务和高可用复制等核心模块。

三、核心组件深度解析

NameServer:轻量级服务发现枢纽

NameServer是RocketMQ的轻量级服务发现与路由中心, 其核心目标是实现生产消费与Broker服务的解耦。 它不存储消息数据,仅管理路由元数据。

核心是一张的路由表 HashMap<String/* Topic */, List>,记录了每个Topic对应在哪些Broker的哪些队列上。

客户端内置了故障规避机制。如果从某个NameServer获取路由失败,或根据路由信息访问Broker失败,会自动重试其他NameServer或Broker。

1. 核心角色与设计哲学: NameServer的设计哲学是 “简单、无状态、最终一致” 。 每个NameServer节点独立运行,节点间互不通信, 这使其具备极强的水平扩展能力和极高的可用性。客户端会配置所有NameServer地址,并向其广播请求。

2. 核心工作机制: 其运作围绕路由信息的生命周期展开,可通过下图一览其核心流程:

3. 和kafka注册中心对比

  • NameServer 采用 “去中心化” 和 “最终一致” 思想,追求极致的简单、轻量和水平扩展, 牺牲了强一致性,以换取架构的简洁和高可用。这非常适合路由信息变动不频繁、客户端具备容错能力的消息场景。
  • Kafka (KRaft) 采用 “中心化” 和 “强一致” 思想,追求数据的精确和系统的自包含。 它将元数据管理深度内化,通过共识协议保证全局一致,但代价是架构复杂度和运维成本更高。

优劣分析: NameServer在运维简易性、集群扩展性、无外部依赖上占优;而Kafka KRaft在元数据强一致性、系统自包含、架构统一性上更胜一筹。选择取决于你对一致性、复杂度、运维成本的具体权衡。

Broker:消息存储与转发的核心引擎

解密存储文件设计

Broker目录下的文件结构

所有核心存储文件均位于Broker节点的 ${storePathRootDir}/store/ 目录下(默认路径为 ~/store/),其下各子目录职责分明:

目录/文件 核心职责 关键设计说明
commitlog/ 消息实体存储库 • 设计:所有Topic的消息顺序混合追加写入。• 文件:以起始物理偏移量命名(20位数字),默认每个1GB。lock文件确保同一时刻只有一个进程写入,保障严格顺序写。
consumequeue/ 逻辑消费队列索引 • 结构:按 {Topic}/{QueueId}/三级目录组织。 • 文件:存储定长记录(20字节/条),包含物理偏移量、长度和Tag哈希码。 • 作用:为消费者提供按Topic和队列分组的逻辑视图,实现高效拉取。
index/ 消息键哈希索引 • 文件:以创建时间戳命名(如20240515080000000)。 • 结构:采用 “哈希槽 + 链表” 结构。 • 用途:支持根据 Message Key 或时间范围进行消息查询,用于运维排查。
config/ 运行时元数据 • 存储Broker运行期间生成的动态数据,如所有Topic的配置消费者组的消费进度(offset) 等。
checkpoint 状态检查点文件 • 记录 commitlog、consumequeue、index等文件最后一次刷盘的时间戳,用于崩溃恢复时确定数据恢复的起点。
abort 异常关闭标志文件 • 该文件存在即表明Broker上一次是非正常关闭,重启时会触发恢复流程。
lock 锁文件 • lock文件确保同一时刻只有一个进程写入,保障严格顺序写。

commitLog

消息主体以及元数据的存储主体, 存储Producer端写入的消息主体内容,消息内容不是定长的。 单个文件大小默认1G, 文件名长度为20位,左边补零,剩余为起始偏移量, 比如00000000000000000000代表了第一个文件,起始偏移量为0,文件大小为1G=1073741824;当第一个文件写满了,第二个文件为00000000001073741824,起始偏移量为1073741824,以此类推。消息主要是顺序写入日志文件,当文件满了,写入下一个文件;

当我们消息发送到RocketMQ以后,消息在commitLog中,因为body大小是不固定的,所以每个消息的长度也是不固定的,其存储格式如下:

下面每个表格列举了每个字段的含义

字段 字段名 数据类型 字节数 说明与用途
1 MsgLen / TOTALSIZE int 4 消息总长度,即从本字段开始到结束的总字节数,是解析消息的起点。
2 MagicCode int 4 魔术字,固定值(如 0xdaa320a7),用于标识这是一个有效的消息存储起始点,也用于区分消息体文件末尾空白填充区
3 BodyCRC int 4 消息体内容的CRC校验码, 用于校验消息体在存储过程中是否损坏。
4 QueueId int 4 队列ID,标识此消息属于Topic下的哪个逻辑队列。
5 Flag int 4 消息标志位,供应用程序自定义使用,RocketMQ内部未使用。
6 QueueOffset long 8 消费队列偏移量,即此消息在其对应ConsumeQueue中的顺序索引,是连续的
7 PhysicalOffset long 8 物理偏移量,即此消息在所有CommitLog文件中的起始字节偏移量。由于消息长度不定,此偏移量不是连续的
8 SysFlag int 4 系统标志位,是一个二进制组合值,用于标识消息特性,如:是否压缩、是否为事务消息、是否等待事务提交等。
9 BornTimestamp long 8 消息生成时间戳,由Producer客户端在发送时生成。
10 BornHost 8字节 8 消息发送者地址。其编码并非简单字符串,而是将IP的4个段和端口号的2个字节,共6个字节,按大端序组合并填充到8字节中。
11 StoreTimestamp long 8 消息存储时间戳,即Broker收到消息并写入内存的时间。
12 StoreHost 8字节 8 Broker存储地址,编码方式同BornHost。
13 ReconsumeTimes int 4 消息重试消费次数,用于死信队列判断。
14 PreparedTransationOffset long 8 事务消息专用,存储与之关联的事务日志(Transaction Log)的偏移量
15 BodyLength int 4 消息体实际长度,后跟Body内容。
16 Body byte[] 不定 消息体内容,即Producer发送的原始业务数据。
17 TopicLength byte 1 Topic名称的长度(1字节,因此Topic名不能超过255字符)。
18 Topic byte[] 不定 Topic名称的字节数组。
19 PropertiesLength short 2 消息属性长度,后跟Properties内容。
20 Properties byte[] 不定 消息属性,用于存储用户自定义的Key-Value扩展信息。在编码时,Key和Value之间用特殊不可见字符(如\u0001)分隔,因此属性中不能包含这些字符。

ConsumeQueue

消息消费索引,引入的目的主要是提高消息消费的性能。 由于RocketMQ是基于主题topic的订阅模式,消息消费是针对主题进行的,如果要遍历commitlog文件,根据topic检索消息是非常低效的。

为了解决这个问题中,提高消费时候的速度,RocketMQ会启动后台的 dispatch 线程源源不断的将消息从 commitLog 取出消息在 CommitLog 中的物理偏移量,消息长度以及 Tag Hash 等信息作为单条消息的索引,分发到对应的消费队列,构成了对 CommitLog 的引用。

consumer可根据ConsumeQueue来查找待消费的消息。其中,ConsumeQueue作为消费消息的索引,保存了指定Topic下的队列消息在CommitLog中的起始物理偏移量offset,消息大小size和消息Tag的HashCode值。

consumequeue文件可以看成是基于topic的commitlog索引文件, 故consumequeue文件夹的组织方式如下:

$HOME/store/consumequeue/{topic}/{queueId}/{fileName}

consumequeue文件采取定长设计, 每一个条目共20个字节,前8字节的commitlog物理偏移量、中间4字节的消息长度、8字节tag的hashcode。

indexFile

RocketMQ的IndexFile索引文件提供了通过消息Key或时间区间查询消息的能力,其存储路径为$HOME/store/index/{fileName},其中文件名以创建时间戳命名。单个IndexFile文件大小固定约为400M,可保存2000W个索引,其底层采用类HashMap的哈希索引结构实现。

IndexFile是一个固定大小的文件(约400MB),其物理结构由三部分组成

1.IndexHeader(索引头,40字节)

beginTimestamp: 第一条消息存储时间

endTimestamp: 最后一条消息存储时间

beginPhyoffset: 第一条消息在CommitLog中的物理偏移量

endPhyoffset: 最后一条消息在CommitLog中的物理偏移量

hashSlotCount: 已使用的哈希槽数量

indexCount: 索引单元总数

2.Slots(哈希槽)

每个IndexFile包含500万个哈希槽位,每个Slot槽位(4字节)存储的是链式索引的第一个索引序号,每个槽位可挂载多个索引单元,形成链式结构。

  • 如果Slot值为0:表示该槽位没有索引链
  • 如果Slot值为N:表示该槽位对应的索引链头节点索引序号为N

3.Indexes(索引单元,20字节/个)

每个索引单元包含以下字段:

  • keyHash: 消息Key的哈希值
  • phyOffset: 消息在CommitLog中的物理偏移量
  • timeDiff: 消息存储时间与IndexFile创建时间的差值
  • preIndexNo: 同一哈希槽中前一个索引单元的序号

这个结构和hashmap结构很像,但是支持每个key通过时间排序,就可以进行时间范围的检索。

通过定长索引结构和整体设计可以通过key快速定位索引数据,拿到真实数据的物理偏移量。

4.索引查询流程

消费者通过消息Key查询时,执行以下步骤:

  1. 计算槽位序号slot序号 = key哈希值 % 500万
  2. 定位槽位地址slot位置 = 40 + (slot序号 - 1) × 4
  3. 获取首个索引位置index位置 = 40 + 500万 × 4 + (索引序号 - 1) × 20
  4. 遍历索引链从槽位指向的索引开始,沿preIndexNo链式查找,匹配目标Key并校验时间范围
  5. 获取物理偏移量从匹配的索引单元中读取phyOffset,最终从CommitLog获取完整消息内容

通过此机制,IndexFile实现了基于Key的高效点查和基于时间范围的快速检索。

整体流程

RocketMQ 高性能存储的核心,在于其 “混合存储” 架构,这正是一种精妙的存储层读写分离设计。

其工作流程可以这样理解:

  1. 统一写入,保证极致性能: 所有消息顺序追加写入一个统一的 CommitLog 文件。这种单一的顺序写操作,是它能承受海量消息写入的根本。
  2. 异步构建,优化读取路径: 消息一旦持久化至 CommitLog,即视为安全。随后,后台服务线程会异步地构建出专供消费的 ConsumerQueue(逻辑队列索引)和用于查询的 IndexFile。这相当于为数据建立了高效的“目录”。
  3. 消费消息: 消费者实际拉取消息时,是先读取 ConsumerQueue 找到消息在 CommitLog 中的物理位置,再反查 CommitLog 获取完整消息内容。
  4. 可靠的消费机制: 基于上述持久化保障,配合消费者自身的偏移量管理及Broker的长轮询机制,共同实现了消息的可靠投递与高效获取。

这种 “读写分离” 设计的好处在于:将耗时的写操作(顺序写CommitLog)与复杂的读操作(构建索引、分散查询)解耦,让两者可以异步、独立地进行优化,从而在整体上获得更高的吞吐量和更低的延迟。这体现了“各司其职,异步协同”的经典架构思想。

下图是官方文档的流程图

写入流程

1.消息预处理

基础校验: 检查Topic名称、消息体长度等是否合法。

生成唯一ID: 结合Broker地址和CommitLog偏移量等,生成全局唯一的MsgID。

设置系统标志: 根据消息属性(如是否事务消息、是否压缩)设置SysFlag。

2.CommitLog核心写入

获取MappedFile: 根据当前写入位置,定位或创建对应的1GB内存映射文件。这里采用双重检查锁模式来保证性能和安全。

串行加锁写入: 获取全局或文件级锁(PutMessageLock),确保同一时刻只有一个线程写入文件,严格保证顺序性。

序列化与追加: 将消息按照之前分析的二进制协议, 序列化到MappedByteBuffer中,并更新写入指针。

3.刷盘(Flush)

同步刷盘: 消息写入内存映射区后,会创建一个GroupCommitRequest并放入请求组。写入线程会等待,直到刷盘线程完成该请求对应文件的物理刷盘后,才返回成功给Producer。数据最可靠,但延迟较高。

异步刷盘(默认): 消息写入内存映射区后,立即返回成功给Producer。同时唤醒异步刷盘线程, 该线程会定时或当PageCache中待刷盘数据积累到一定量时,执行一次批量刷盘。性能高,但有宕机丢数风险。

4.异步索引构建

由独立的ReputMessageService线程处理。它不断检查CommitLog中是否有新消息到达。

一旦有新消息被确认持久化(对于同步刷盘是已落盘,对于异步刷盘是已写入映射区),该线程就会读取消息内容。

随后,它会为这条消息在对应的consumequeue目录下构建消费队列索引(记录CommitLog物理偏移量和消息长度),更新index索引文件。

消费流程

1.启动与负载均衡

消费者启动后,会向NameServer获取Topic的路由信息(包含哪些队列、分布在哪些Broker上)。

如果消费者组内有多个实例,会触发队列负载均衡(默认策略是平均分配)。例如,一个Topic有8个队列,两个消费者实例,则通常每个消费者负责消费4个队列。这一步决定了每个消费者“认领”了哪些消息队列。

2.拉取消息循环

每个消费者实例内部都有一个PullMessageService线程,它循环从一个PullRequest队列中获取任务。

PullRequest包含了拉取目标(如Broker-A, 队列3)以及下一次要拉取的位点(offset)。

消费者向指定的Broker发送网络请求,请求体中就携带了这个offset。

3.Broker端处理与返回

Broker收到请求后,根据Topic、队列ID和offset,去查询对应的ConsumeQueue索引文件。

ConsumeQueue中存储的是定长(20字节)的记录,包含消息在CommitLog中的物理偏移量和长度。

Broker根据物理偏移量,从CommitLog文件中读取完整的消息内容,通过网络返回给消费者。

4.消息处理与位点提交

消费者将拉取到的消息提交到内部的消费线程池进行处理,你的业务逻辑就在这里执行。

消费位点的管理至关重要:

位点存储: 位点由OffsetStore管理。在集群模式(CLUSTER) 下,消费位点存储在Broker上;在广播模式(BROADCAST) 下,位点存储在本地。

位点提交: 消费成功后,消费者会异步(默认方式)向Broker提交已消费的位点。Broker将其持久化到store/config/consumerOffset.json文件中。

5.消息重试与死信

如果消息消费失败(抛出异常或超时未返回CONSUME_SUCCESS),RocketMQ会触发重试机制。

对于普通消息,消息会被发回Broker上一个特殊的重试主题(%RETRY%),延迟一段时间(延迟级别:1s、5s、10s…)后再被原消费者组拉取。

如果重试超过最大次数(默认16次),消息会被投递到死信主题(%DLQ%),等待人工干预。死信队列中的消息不会再被自动消费。

一体与分离:Kafka和RocketMQ的核心架构博弈

说起RocketMQ就不能不提起Kafka了,两者都是消息中间件这个领域的霸主,但它们的核心架构设计差异, 直接决定了各自不同的性能特性和适用场景,这也是技术选型时必须深入理解的重点。

核心架构设计差异

Kafka:读写一体的“分区日志”模型, Kafka的架构哲学是极简与统一。 它将每个主题分区抽象为一个仅追加(append-only)的物理日志文件。 生产者和消费者都直接与这个日志文件交互:生产者顺序写入尾部,消费者通过维护偏移量顺序读取。这种设计下,数据的读写路径完全一致, 逻辑与物理结构高度统一。

RocketMQ:读写分离的“二级制”模型 , RocketMQ的架构哲学是分工与优化。 它采用了物理CommitLog + 逻辑ConsumeQueue的二级结构。 所有消息都顺序写入一个统一的CommitLog物理文件,实现磁盘的最高效顺序写。同时,为每个消息队列异步构建一个轻量级的ConsumeQueue索引文件,消费者读取时先查询内存中的ConsumeQueue定位,再到CommitLog中获取消息体。这是一种逻辑与物理分离的设计。

优劣势对比

基于上述架构设计根本差异,两者在关键指标上各显优劣:

维度 Kafka(读写一体) RocketMQ(读写分离)
核心优势 极致吞吐与低延迟:读写同路径,数据写入后立即可读,端到端延迟极低。架构简单:无中间状态,副本同步、故障恢复逻辑清晰。 高并发读与丰富功能:索引与数据分离,支持海量消费者并发读。业务友好:原生支持事务消息、定时/延时消息、消息轨迹查询。
存储效率 磁盘顺序IO最大化:生产和消费都是严格顺序IO,尤其适合机械硬盘。 写性能极致化:所有消息顺序写CommitLog,但存在“写放大” ,一条消息需写多次(1次CommitLog + N次ConsumeQueue)。
读性能 消费者落后时可能触发随机读:若消费者要读取非尾部历史数据,可能需磁盘寻道。但现代SSD和预读机制已大大缓解此问题。 读路径优化:ConsumeQueue小而固定,可全量缓存至内存,读操作变为“内存寻址 + CommitLog顺序/随机读”。在PageCache命中率高时表现优异。
扩展性与成本 文件句柄(inode)开销大:每个分区都是独立目录和文件,海量分区时运维成本高。 存储成本与效率更优:多Topic共享CommitLog,文件数少,特别适合中小消息体、多Topic的场景
典型场景 日志流、指标监控、实时流处理:作为大数据管道,与Flink/Spark生态无缝集成。 电商交易、金融业务、异步解耦:需要严格顺序、事务保障、业务查询的在线业务场景。

总而言之,Kafka像一个设计精良的高速公路系统, 核心目标是让数据车辆(消息)能够高吞吐、低延迟地持续流动,并方便地引向各个处理工厂(流计算)。而RocketMQ则像一个高度可靠的快递网络, 不仅确保包裹(消息)准确送达,还提供预约配送(定时)、签收确认(事务)、异常重投(重试)等一系列服务于业务逻辑的增值功能。

RocketMQ对于随机读取的优化

RocketMQ在消费时候的流程

消费者请求 → ConsumeQueue(内存/顺序)获取commitlog上的物理偏移量 → 根据物理偏移量定位CommitLog(磁盘/随机) → 返回消息

从ConsumeQueue获取到消息在commitlog中的偏移量的时候,回查时候可能产生随机IO

  1. 第一次随机IO: 根据ConsumeQueue中的物理偏移量,在CommitLog中定位消息位置
  2. 可能的连续随机IO: 如果一次拉取多条消息,这些消息在CommitLog中可能物理不连续

为了保证RocketMQ的高性能,采用一些优化措施,尽量避免随机IO

1. ConsumeQueue的内存映射优化

实际上,RocketMQ将ConsumeQueue映射到内存,每个ConsumeQueue约5.72MB,可完全放入PageCache,读索引操作几乎是内存操作。

public class ConsumeQueue {
    private MappedFile mappedFile;  // 内存映射文件
    // 20字节每条:8(offset) + 4(size) + 8(tagHashCode)
}

2. PageCache的充分利用

Linux PageCache工作流程: 

  1. 消息写入CommitLog → 进入PageCache
  2. 消费者读取 → 优先从PageCache获取
  3. 如果PageCache命中:内存速度(≈100ns)
  4. 如果PageCache未命中:磁盘随机读取(≈10ms)

3. 批量读取优化

// DefaultMessageStore.java
public GetMessageResult getMessage(...) {
    // 一次读取多条消息(默认最多32条)
    // 即使这些消息物理不连续,通过批量读取减少IO次数
    for (int i = 0; i < maxMsgNums; i++) {
        // 使用同一个文件channel批量读取
        readMessage(ctx, msgId, consumerGroup);
    }
}

4. 读取顺序性的保持

虽然CommitLog中不同Topic的消息是随机存放的,但同一个Queue的消息在CommitLog中是基本连续的:

Queue1: | Msg1 | Msg3 | Msg5 | ... | 在ConsumeQueue中连续
        ↓      ↓      ↓
CommitLog: | Msg1 | Msg2(T2) | Msg3 | Msg4(T3) | Msg5 |
          ↑_________________________↑
          物理上相对连续,减少磁头寻道

高可用设计:双轨并行的可靠性架构

主从架构(Master-Slave)

经典主从模式: RocketMQ早期采用Master-Slave架构,Master处理所有读写请求,Slave仅作为热备份。这种模式下,故障切换依赖人工干预或半自动脚本, 恢复时间通常在分钟级别。

Dledger高可用集群: RocketMQ 4.5引入的Dledger基于Raft协议实现真正的主从自动切换。 当Master故障时,集群能在秒级(通常2-10秒)内自动选举新Leader,期间消息仍可写入(写入请求会阻塞至新Leader选出)。

多副本机制: 现代部署中,建议采用2主2从或3主3从架构。例如在阿里云上,每个Broker组包含1个Master和2个Slave,形成跨可用区的三副本, 单机房故障不影响服务可用性。

同步/异步复制

同步复制保证强一致(消息不丢失),异步复制追求更高性能。

// Broker配置示例
brokerRole = SYNC_MASTER
// 生产者发送消息后,必须等待至少一个Slave确认
// 确保即使Master宕机,消息也不会丢失
  • 强一致性保证:消息写入Master后,同步复制到Slave才返回成功
  • 性能代价:延迟增加约30-50%,TPS下降约20-40%
  • 适用场景:金融交易、资金变动等对数据一致性要求极高的业务

同步/异步刷盘

同步刷盘保证消息持久化不丢失,异步刷盘提升吞吐。

brokerRole = ASYNC_MASTER
// 消息写入Master即返回成功,Slave异步复制
// 存在极短时间的数据丢失风险
  • 高性能模式: 延迟降低,吞吐量接近单节点性能
  • 风险窗口: Master宕机且数据未同步时,最近几秒消息可能丢失
  • 适用场景: 日志收集、监控数据、可容忍微量丢失的业务消息

刷盘策略的工程优化

同步刷盘(SYNC_FLUSH)

生产者 → Broker内存 → 磁盘强制刷盘 → 返回成功
  • 零数据丢失: 即使机器掉电,消息也已持久化到磁盘
  • 性能瓶颈: 每次写入都触发磁盘IO,机械硬盘下TPS通常<1000
  • 优化手段: 使用SSD硬盘可大幅提升性能

异步刷盘(ASYNC_FLUSH)

生产者 → Broker内存 → 立即返回成功 → 异步批量刷盘
  • 高性能选择: 依赖PageCache,SSD下TPS可达数万至数十万
  • 可靠性依赖: 依赖操作系统的刷盘机制(通常5秒刷盘一次)
  • 配置调优:
# 调整刷盘参数
flushCommitLogLeastPages = 4    # 至少4页(16KB)才刷盘
flushCommitLogThoroughInterval = 10000  # 10秒强制刷盘一次

四、Producer与Consumer:高效的生产与消费模型

Producer

消息路由策略:

// 内置多种队列选择算法
DefaultMQProducer producer = new DefaultMQProducer("ProducerGroup");
// 1. 轮询(默认):均匀分布到所有队列
// 2. 哈希:相同Key的消息路由到同一队列,保证局部顺序
// 3. 机房就近:优先选择同机房的Broker
producer.send(msg, new MessageQueueSelector() {
    @Override
    public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
        // 自定义路由逻辑
        return mqs.get(arg.hashCode() % mqs.size());
    }
});

发送模式对比:

模式 特点 性能 适用场景
同步发送 阻塞等待Broker响应 TPS约5000-20000 重要业务消息,需立即知道发送结果
异步发送 回调通知结果 TPS可达50000+ 高并发场景,如日志、监控数据
单向发送 发送后不等待 TPS最高(100000+) 可容忍少量丢失的非关键数据

失败重试与熔断:

  • 智能重试: 发送失败时自动重试(默认2次),可配置退避策略
  • 故障规避: 自动检测Broker可用性,故障期间路由到健康节点
  • 慢请求熔断: 统计发送耗时,自动隔离响应慢的Broker

Consumer

负载均衡策略:

// 集群模式:同一ConsumerGroup内消费者均分队列
consumer.setMessageModel(MessageModel.CLUSTERING);
// 广播模式:每个消费者消费全量队列
consumer.setMessageModel(MessageModel.BROADCASTING);

消费进度管理:

Broker托管: 默认方式,消费进度存储在Broker

本地维护: 某些场景下可自主管理offset(如批量处理)

重置策略:

// 支持多种消费起点
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);  // 从最后
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); // 从头
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_TIMESTAMP);    // 从时间点

并发控制优化:

// 关键并发参数
consumer.setConsumeThreadMin(20);     // 最小消费线程数
consumer.setConsumeThreadMax(64);     // 最大消费线程数
consumer.setPullBatchSize(32);        // 每次拉取消息数
consumer.setConsumeMessageBatchMaxSize(1); // 批量消费大小
// 流控机制
consumer.setPullThresholdForQueue(1000);  // 队列堆积阈值
consumer.setPullInterval(0);              // 拉取间隔(0为长轮询)

五、核心流程与特性背后的架构支撑

1 .顺序消息如何保证?

全局顺序: 单Topic单队列(牺牲并发)。

分区顺序: 通过MessageQueue选择器确保同一业务键(如订单ID)的消息发往同一队列,Consumer端按队列顺序消费。

2.事务消息的两阶段提交

流程详解: Half Message -> 执行本地事务 -> Commit/Rollback。

架构支撑: Op消息回查机制,解决分布式事务的最终一致性,是架构设计中“状态可回溯”思想的体现。

3.延时消息的实现奥秘

并非真正延迟投递: 为不同延迟级别预设独立的SCHEDULE_TOPIC, 定时任务扫描到期后投递至真实Topic。

设计权衡: 以存储和计算换取功能的灵活与可靠。

六、其他性能优化关键技术点

  1. 零拷贝(Zero-copy): 通过sendfile或mmap+write方式,减少内核态与用户态间数据拷贝,大幅提升网络发送与文件读写效率。
  2. 堆外内存与内存池: 避免JVM GC对大数据块处理的影响,实现高效的内存管理。
  3. 文件预热: 启动时将存储文件映射到内存并写入“假数据”,避免运行时缺页中断。

七、总结:RocketMQ架构设计的启示

RocketMQ的架构设计,尤其是其在简洁性、高性能和云原生演进方面的平衡,为构建现代分布式系统提供了许多宝贵启示。

  1. 在简单与完备间权衡: RocketMQ没有采用强一致性的ZooKeeper,而是自研了极其简单的NameServer。这说明在非核心路径上,牺牲一定的功能完备性来换取简单性和高可用性,可能也是个不错的选择。
  2. 以写定存储,以读优查询: 其存储架构是典型的写优化设计。所有消息顺序追加写入,保证了最高的写入性能。而针对消费和查询这两种主要的“读”场景,则分别通过异步构建索引数据结构(ConsumeQueue和IndexFile)来优化。

八、参考资料

往期回顾

1.PAG在得物社区S级活动的落地

2.Ant Design 6.0 尝鲜:上手现代化组件开发|得物技术 

3.Java 设计模式:原理、框架应用与实战全解析|得物技术

4.Go语言在高并发高可用系统中的实践与解决方案|得物技术

5.从0到1搭建一个智能分析OBS埋点数据的AI Agent|得物技术

文 /磊子

关注得物技术,每周更新技术干货

要是觉得文章对你有帮助的话,欢迎评论转发点赞~

未经得物技术许可严禁转载,否则依法追究法律责任。

❌
❌