目录
前言
RocketMQ 是阿里开源的分布式消息中间件,跟其它中间件相比,RocketMQ 的特点是纯JAVA实现。
架构图如下所示:
天然弊端:
RocketMQ 采用一个 consumer 绑定一个或者多个 Queue 模式,假如某个消费者服务器挂了,则会造成部分Queue消息堆积。
而kafka中的分区会采用reblace机制,重新分配消费者。
1、核心组件
1.1、 基础概念
Producer:
消息生产者,负责产生消息,一般由业务系统负责产生消息。
Producer Group:
消息生产者组,简单来说就是多个发送同一类消息的生产者称之为一个生产者。
Consumer:
消息消费者,负责消费消息,一般是后台系统负责异步消费。
Consumer Group:
消费者组,和生产者类似,消费同一类消息的多个 Consumer 实例组成一个消费者组。
Topic:
主题,用于将消息按主题做划分,Producer将消息发往指定的Topic,Consumer订阅该Topic就可以收到这条消息。
Message:
消息,每个message必须指定一个topic,Message 还有一个可选的 Tag 设置,以便消费端可以基于 Tag 进行过滤消息。
Tag:
标签,子主题(二级分类)对topic的进一步细化,用于区分同一个主题下的不同业务的消息。
Broker:
Broker是RocketMQ的核心模块,负责接收并存储消息,同时提供Push/Pull接口来将消息发送给Consumer。
Broker同时提供消息查询的功能,可以通过MessageID和MessageKey来查询消息。Borker会将自己的Topic配置信息实时同步到NameServer。
Queue:
Topic和Queue是1对多的关系,一个Topic下可以包含多个Queue,主要用于负载均衡,Queue数量设置建议不要比消费者数少。发送消息时,用户只指定Topic,Producer会根据Topic的路由信息选择具体发到哪个Queue上。Consumer订阅消息时,会根据负载均衡策略决定订阅哪些Queue的消息。
Offset:
RocketMQ在存储消息时会为每个Topic下的每个Queue生成一个消息的索引文件,每个Queue都对应一个Offset记录当前Queue中消息条数。
NameServer:
NameServer可以看作是RocketMQ的注册中心,它管理两部分数据:集群的Topic-Queue的路由配置;Broker的实时配置信息。其它模块通过Nameserv提供的接口获取最新的Topic配置和路由信息;各 NameServer 之间不会互相通信, 各 NameServer 都有完整的路由信息,即无状态。
Producer/Consumer :通过查询接口获取Topic对应的Broker的地址信息和Topic-Queue的路由配置
Broker :
注册配置信息到NameServer, 实时更新Topic信息到NameServer
1.2、顺序消息原理
1、顺序消息支持
RocketMQ 提供了严格的顺序消息处理能力,无论是在分发消息还是消费消息,都能够保证相同分组的消息按顺序处理。
2、顺序消息原理
1.消息队列:
在RocketMQ中,一个Topic会分为多个消息队列(Queue)。消息的顺序是通过队列保证的。生产者在发送消息时,根据某种规则(如消息的key)将属于同一组的消息发送到同一个队列中。
如下图所示:
2.单线程消费:
消费者从某个队列中按顺序拉取消息。为了保证顺序,该队列的消息通常会由消费者的同一线程处理。
3.消息发送策略:
生产者可以通过选择某种消息发送策略,将需要严格顺序处理的消息发送到同一个Queue,这通常通过消息中的业务key进行hash计算,然后选择一个具体的Queue。
4.故障恢复:
如果队列中的消息因为消费失败而需要重试,RocketMQ支持将消费失败的消息重新放回到队列的头部,从而保持顺序性。
1.3、设计理念及性能
-
RocketMQ:
- 顺序消息:支持严格顺序消息(全局顺序或分区顺序),通过
MessageQueue
和MessageListener
实现。 - 事务消息:支持分布式事务消息(半事务消息 + 回查机制),适合跨系统数据一致性(如订单扣款、库存更新)。
- 延迟消息:支持 18 个固定延迟等级(如 1s、5s、10s 到 2h)。
- 消息标签(Tag):通过 Tag 对消息分类,支持灵活的消费过滤。
- 顺序消息:支持严格顺序消息(全局顺序或分区顺序),通过
-
Kafka:
- 分区顺序:仅保证单分区内的顺序性,需业务层维护全局顺序。
- 无事务消息:原生不支持事务消息(Kafka 2.0+ 支持事务 API,但复杂度高)。
- 延迟消息:需通过定时任务或外部组件实现。
-
RabbitMQ:
- 消息确认机制:支持手动/自动确认,但不支持事务消息。
- 延迟消息:需通过插件(如
rabbitmq_delayed_message_exchange
)实现。 - 消息优先级:支持消息优先级队列。
-
ActiveMQ:
- JMS 标准:完全兼容 JMS 1.1,但性能较弱。
- 事务支持:支持本地事务,但不支持跨系统分布式事务。
1.4、可靠性与持久化对比
-
RocketMQ:
- 同步双写:支持 Broker 间同步复制,确保数据不丢失。
- 刷盘策略:异步刷盘(高性能)或同步刷盘(强一致性)。
- 消息回溯:支持任意时间点的消息回溯(消费位点管理)。
-
Kafka:
- ISR 机制:通过副本同步队列(In-Sync Replicas)保障数据可靠性。
- 持久化:消息持久化到磁盘,但依赖副本数和
acks
配置。 - 消息回溯:需手动维护消费偏移量(offset)。
-
RabbitMQ:
- 持久化队列/消息:通过
durable
和persistent
标志实现。 - 镜像队列:主备节点同步,但性能开销较大。
- 持久化队列/消息:通过
-
ActiveMQ:
- KahaDB/KahaMQ:默认持久化存储,但性能较弱。
- 主备模式:支持 Master-Slave 架构,但切换复杂。
2、RocketMQ 消费模式
2.1、广播模式
一条消息被多个Consumer消费,即使这些Consumer属于同一个Consumer Group,消息也会被Consumer Group中的每一个Consumer都消费一次。(消费者和queue为强绑定)
//设置广播模式
consumer.setMessageModel(MessageModel.BROADCASTING);
2.2、集群模式
一个Consumer Group中的所有Consumer平均分摊消费消息(组内负载均衡)。
//设置集群模式,也就是负载均衡模式
consumer.setMessageModel(MessageModel.CLUSTERING);
3、基础架构
3.1、NameServer介绍
rocketMq使用轻量级的NameServer服务进行服务的协调和治理工作,NameServer多节点部署时相互独立互不干扰。
每一个rocketMq服务节点(broker节点)启动时都会遍历配置的NameServer列表并建立长链接,broker节点每30秒向NameServer发送一次心跳信息、NameServer每10秒会检查一次连接的broker是否存活。
消费者和生产者会随机选择一个NameServer建立长连接,通过定期轮训更新的方式获取最新的服务信息。
架构简图如下:
NameServer:
启动,监听端口,等待producer,consumer,broker连接上来
Broker:
启动,与nameserver保持长链接,定期向nameserver发送心跳信息,包含broker的ip,端口,当前broker上topic的信息
producer:
启动,随机选择一个NameServer建立长连接,拿到broker的信息,然后就可以给broker发送消息了
consumer:
启动,随机选择一个NameServer建立长连接,拿到broker的信息,然后就可以建立通道,消费消息
3.2、Broker介绍
RocketMQ 存储用的是本地文件存储系统,将所有topic的消息全部写入同一个文件中(commit log),这样保证了IO写入的绝对顺序性,最大限度利用IO系统顺序读写带来的优势提升写入速度。
由于消息混合存储在一起,需要将每个消费者组消费topic最后的偏移量记录下来。这个文件就是consumer queue(索引文件)。所以消息在写入commit log 文件的同时还需将偏移量信息写入consumer queue文件。在索引文件中会记录消息的物理位置、偏移量offse,消息size等,消费者消费时根据上述信息就可以从commit log文件中快速找到消息信息。
Broker 存储结构如下:
3.2、存储文件简介
1、Commit log:
消息存储文件,rocket Mq会对commit log文件进行分割(默认大小1GB),新文件以消息最后一条消息的偏移量命名。(比如 00000000000000000000 代表了第一个文件,第二个文件名就是 00000000001073741824,表明起始偏移量为 1073741824)。
2、Consumer queue:
消息消费队列(也是个文件),可以根据消费者数量设置多个,一个Topic 下的某个 Queue,每个文件约 5.72M,由 30w 条数据组成;ConsumeQueue 存储的条目是固定大小,只会存储 8 字节的 commitlog 物理偏移量,4 字节的消息长度和 8 字节 Tag 的哈希值,固定 20 字节;消费者是先从 ConsumeQueue 来得到消息真实的物理地址,然后再去 CommitLog 获取消息。
3、IndexFile:
索引文件,是额外提供查找消息的手段,通过 Key 或者时间区间来查询对应的消息
3.4、消费流程
Producer 使用轮询的方式分别向每个 Queue 中发送消息。
Consumer 启动的时候会在 Topic,Consumer group消费者组 维度发生负载均衡,为每个客户端分配需要处理的 Queue。
负载均衡过程中每个客户端都获取到全部的的 ConsumerID 和所有 Queue 并进行排序,每个客户端使用相同负责均衡算法,例如平均分配的算法,这样每个客户端都会计算出自己需要消费那些 Queue,每当 Consumer 增加或减少就会触发负载均衡,所以我们可以通过 RocketMQ 负载均衡机制实现动态扩容,提升客户端收发消息能力。
客户端负责均衡为客户端分配好 Queue 后,客户端会不断向 Broker 拉取消息,在客户端进行消费。
注意:
可以一直增加客户端的数量提升消费能力吗?当然不可以,因为 Queue 数量有限,客户端数量一旦达到 Queue 数量,再扩容新节点无法提升消费能力,因为会有节点分配不到 Queue 而无法消费。
3.5、消费负载均衡策略
topic 在创建之处可以设置 comsumer queue数量。而 comsumer 在启动时会和comsumer queue绑定,这个绑定策略是咋样的?
如下图所示:
1、默认策略:
queue 个数大于 Consumer个数, 那么 Consumer 会平均分配 queue,不够平均,会根据clientId排序来拿取余数。
queue个数小于Consumer个数,那么会有Consumer闲置,就是浪费掉了,其余Consumer平均分配到queue。
2、一致性hash算法
就近元则,离的近的消费。每个消费者依次消费一个queue,环状。
4、数据持久化
刷盘策略 是消息持久化的核心机制,决定了消息从内存写入磁盘的方式和时机。它直接影响系统的 可靠性、性能 和 可用性。
如下图所示:
4.1、刷盘策略
RocketMQ 提供了两种主要的刷盘策略:同步刷盘 和 异步刷盘。
1、同步刷盘(Synchronous Flush)
- 机制:
- 生产者发送消息后,Broker 将消息写入内存缓冲区(PageCache),然后 立即调用文件刷盘操作,等待磁盘 IO 完成后再返回生产者结果。
- 刷盘操作由 主线程(HandleFlushThread)负责,确保消息落盘后才确认消息写入成功。
- 优点:
- 强一致性:消息写入磁盘后才返回成功,确保消息不会丢失(即使 Broker 崩溃)。
- 适用于对数据可靠性要求极高的场景(如金融交易、支付系统)。
- 缺点:
- 性能较低:由于需要等待磁盘 IO 完成,吞吐量受限,延迟较高。
- 依赖磁盘 IO 性能,可能成为性能瓶颈。
- 配置示例:
# 在 broker 配置文件中设置
flushDiskType=SYNC_FLUSH
2、异步刷盘(Asynchronous Flush)
- 机制:
- 生产者发送消息后,Broker 将消息写入内存缓冲区(PageCache),不等待磁盘 IO 完成,直接返回生产者成功。
- 后台有一个独立的 刷盘线程(FlushRealTimeService),定期(默认 500ms)或当内存缓冲区满时,将消息刷入磁盘。
- 优点:
- 高性能:无需等待磁盘 IO,吞吐量高,延迟低。
- 适用于高并发、低延迟的场景(如日志收集、事件流处理)。
- 缺点:
- 存在消息丢失风险:如果 Broker 崩溃或宕机,未刷盘的消息可能丢失(通常最多丢失 500ms 的数据)。
- 配置示例:
# 在 broker 配置文件中设置
flushDiskType=ASYNC_FLUSH
4.2、刷盘策略的实现
如下图所示:
1. 内存缓冲区(PageCache)
- RocketMQ 使用操作系统的 PageCache 机制缓存消息数据,减少直接磁盘 IO 的开销。
- 当消息写入 PageCache 后,操作系统会异步将数据刷入磁盘(具体时机由操作系统管理)。
2. 刷盘线程(FlushRealTimeService)
- 异步刷盘依赖后台线程
FlushRealTimeService
,它会:- 每隔 500ms 检查内存缓冲区。
- 如果缓冲区数据量超过阈值(默认 1GB),立即触发刷盘。
- 如果缓冲区未满,按固定间隔刷盘。
3. 刷盘频率与性能
- 同步刷盘:每次写入都触发刷盘,性能受限于磁盘 IO。
- 异步刷盘:通过批量刷盘降低 IO 频率,性能显著高于同步刷盘。
5、broker数据同步
5.1、同步机制
RocketMQ 的同步复制机制通过 主从架构 + 同步双写 实现数据强一致性,可靠性和高可用性的核心功能之一,确保即使主 Broker 宕机,数据也不会丢失。
其核心流程如下:
- Master 写入消息并持久化。
- 同步发送消息到 Slave 并等待 ACK。
- 收到 ACK 后返回生产者成功响应。
如下图所示:
5.2、核心目标
- 数据不丢失:主 Broker 写入消息后,必须确保从 Broker 同步完成写入。
- 高可用性:主 Broker 宕机时,从 Broker 可快速接管服务。
- 强一致性:主从 Broker 的消息完全一致,避免数据差异。
5.3、同步复制的核心流程
-
消息写入 Master:
- 生产者发送消息到 Master Broker。
- Master 将消息写入内存缓冲区(PageCache)并持久化到本地磁盘(根据刷盘策略,可能是同步或异步)。
- 如果配置为 同步刷盘,Master 会等待磁盘写入完成后再继续下一步。
-
消息同步到 Slave:
- Master 将消息通过 Remoting 模块 发送给对应的 Slave Broker。
- Slave 接收消息后,同样写入内存缓冲区并持久化到本地磁盘。
- 如果配置为 同步复制,Master 会等待 Slave 返回写入成功的确认(ACK)。
-
事务提交与响应:
- 当 Master 收到 Slave 的 ACK 后,才向生产者返回写入成功的响应。
- 如果 Slave 未在超时时间内返回 ACK,Master 会重试同步,直到成功或放弃(具体行为由配置决定)。
5.4、同步复制的可靠性保障
1. 数据一致性校验
- 同步复制 保证主从 Broker 的 CommitLog 内容完全一致。
- 如果从 Broker 宕机,重启后会从 Master 重新拉取缺失的消息进行同步。
2. 宕机恢复机制
- 主 Broker 宕机:从 Broker 会接管服务,但需人工或自动切换为主角色(RocketMQ 原生不支持自动切换,需依赖外部工具如 Dledger)。
- 从 Broker 宕机:主 Broker 会持续重试同步,直到从 Broker 恢复。
3. 网络异常处理
- 超时重试:Master 会重试发送消息到 Slave,直到成功或达到最大重试次数。
- 断线重连:网络中断后,HAService 会自动尝试重新连接 Slave。
优势:
- 数据不丢失,可靠性高。
- 主从无缝切换,保障服务连续性。
注意事项:
- 性能开销较大,需合理配置刷盘策略和同步超时时间。
- 不支持自动故障转移,需结合 Dledger 或外部工具实现。
6、消费者故障处理
1. 消费者绑定 Queue
- 机制:RocketMQ 的消费者(Consumer)以 Consumer Group 为单位订阅 Topic,并绑定到一个或多个 MessageQueue(队列)。
- 问题:如果某个消费者服务器挂掉,它负责的 Queue 会进入 “空闲”状态,消息无法被消费,导致 消息堆积。
- 原因:
- RocketMQ 的消费者与 Queue 是 强绑定关系,消费者宕机后,Queue 无法自动转移到其他消费者。
- 除非人工干预或重启消费者,否则消息会持续堆积。
2. 解决方案
- 手动恢复:重启宕机的消费者或调整消费者组内的消费者数量。
- 负载均衡策略:通过合理配置消费者组内的消费者数量,避免单点故障。
- 监控告警:实时监控消费者状态,及时发现并处理故障。
总结
- 需要分布式事务和严格顺序:优先选择 RocketMQ。
- 高吞吐流处理或日志聚合:选择 Kafka。
- 复杂路由或多协议支持:选择 RabbitMQ。
- 传统 JMS 场景或小规模系统:选择 ActiveMQ。
参考文章: