RocketMQ原理
架构
Broker Cluster就是各个RocketMQ进程,Producer Cluster和Consumer Cluster分别是生产者和消费者,NameServer Cluster是路由中心。

RocketMQ 一共有四个部分组成:NameServer,Broker,Producer 生产者,Consumer 消费者,它们对应了:发现、发、存、收,为了保证高可用,一般每一部分都是集群部署的。
NameServer
NameServer 是一个无状态的服务器,角色类似于 Kafka 使用的 Zookeeper,但比 Zookeeper 更轻量。
特点:
- 每个 NameServer 结点之间是相互独立,彼此没有任何信息交互。
- Nameserver 被设计成几乎是无状态的,通过部署多个结点来标识自己是一个伪集群,Producer 在发送消息前从 NameServer 中获取 Topic 的路由信息也就是发往哪个 Broker,Consumer 也会定时从 NameServer 获取 Topic 的路由信息,Broker 在启动时会向 NameServer 注册,并定时进行心跳连接,且定时同步维护的 Topic 到 NameServer。
- 每一个NameServer节点都保存着全量的路由信息。因为Broker是集群部署,所以当生产者发送消息时,需要知道将消息发送到哪个Broker,当消息者获取消息时,也需要知道从哪个Broker获取消息。
- RocketMQ的路由发现采用的是Pull模型。当Topic路由信息出现变化时,NameServer不会主动推送给客户端,而是客户端定时拉取主题最新的路由。
功能主要有两个:
- 1、和 Broker 结点保持长连接。
- 2、维护 Topic 的路由信息。
Broker
消息存储和中转角色,负责存储和转发消息。
Broker 内部维护着一个个 Consumer Queue,用来存储消息的索引,真正存储消息的地方是 CommitLog(日志文件)。
单个 Broker 与所有的 Nameserver 保持着长连接和心跳,并会定时将 Topic 信息同步到 NameServer,和 NameServer 的通信底层是通过 Netty 实现的。
Broker负责消息的存储,主从架构,定时和NameServer通信心跳保活。
Broker主从的master是通过raft算法选举出来的。
RocketMQ之所以具有可扩展性,是因为每个Broker节点只保存整体数据的一部分,这样当数据量越来越大时,可以进行水平切分。
Broker在存储消息时,每一个Topic中的所有消息数据可能会分散在不同的Broker节点上,我们可以在创建Topic时进行指定。比如,假设我们的topic_orderInfo包含900万条消息,我们指定其分散在3个Broker节点上,那么每个节点就包含300万条消息数据。
Broker节点集群是一个主备集群,即集群中具有Master与Slave两种角色。Master负责处理读写操作请求,Slave负责对Master中的数据进行备份。当Master挂掉了,Slave则会自动切换为Master去工作。Master和Slave是1:N的关系(N >= 1)
Producer
消息生产者,业务端负责发送消息,由用户自行实现和分布式部署。
- Producer由用户进行分布式部署,消息由Producer通过多种负载均衡模式发送到Broker集群,发送低延时,支持快速失败。
- RocketMQ 提供了三种方式发送消息:同步、异步和单向
- 同步发送:同步发送指消息发送方发出数据后会在收到接收方发回响应之后才发下一个数据包。一般用于重要通知消息,例如重要通知邮件、营销短信。
- 异步发送:异步发送指发送方发出数据后,不等接收方发回响应,接着发送下个数据包,一般用于可能链路耗时较长而对响应时间敏感的业务场景,例如用户视频上传后通知启动转码服务。
- 单向发送:单向发送是指只负责发送消息而不等待服务器回应且没有回调函数触发,适用于某些耗时非常短但对可靠性要求并不高的场景,例如日志收集。
Consumer
消息消费者,负责消费消息,一般是后台系统负责异步消费。
- Consumer也由用户部署,支持 PUSH 和 PULL 两种消费模式,支持集群消费和广播消费,提供实时的消息订阅机制。
- Pull:拉取型消费者(Pull Consumer)主动从消息服务器拉取信息,只要批量拉取到消息,用户应用就会启动消费过程,所以 Pull 称为主动消费型。
- Push:推送型消费者(Push Consumer)封装了消息的拉取、消费进度和其他的内部维护工作,将消息到达时执行的回调接口留给用户应用程序来实现。所以 Push 称为被动消费类型,但其实从实现上看还是从消息服务器中拉取消息,不同于 Pull 的是 Push 首先要注册消费监听器,当监听器处触发后才开始消费消息。
RocketMq也是一个topic分了很多queue,其中一个queue是有序的。 queue类似kafka的partition。
除了Topic外,还有一个Tag分类,区分在于 Topic 是一级分类,而 Tag 可以理解为是二级分类。
消息存储


1、Commit log存储消息实体。顺序写,随机读。
2、Message queue存储消息的偏移量。读消息先读message queue,根据偏移量到commit log读消息本身。 消费者消费消息的进度是基于 ConsumeQueue 来进行管理的。每个消费组(consumer group)会对应一个 ConsumeQueue,它记录了该消费组已消费的消息偏移量,以便在消费者重启时能够恢复消费。
3、索引队列用来存储消息的索引key。 使用mmap方式减少内存拷贝,提高读取性能。具体实现:FileChannel.map(RandomAccessFile)
CommitLog以物理文件的方式存放,每台Broker上的CommitLog被本机器所有ConsumeQueue共享。
在CommitLog,一个消息的存储长度是不固定的,RocketMQ采用了一些机制,尽量向CommitLog中顺序写,但是随即读。
1.CommitLog顺序写,可以大大提高写入的效率;
2.虽然是随机读,但是利用package机制,可以批量地从磁盘读取,作为cache存到内存中,加速后续的读取速度。
3.为了保证完全的顺序写,需要ConsumeQueue这个中间结构,因为ConsumeQueue里只存储偏移量信息,所以尺寸是有限的。在实际情况中,大部分ConsumeQueue能够被全部读入内存,所以这个中间结构的操作速度很快,可以认为是内存读取的速度。
[ 如何保证CommitLog和ConsumeQueue的一致性? ]
CommitLog里存储了Consume Queues、Message Queue、Tag等所有信息,即使ConsumeQueue丢失,也可以通过commitLog完全恢复出来。
QA
不使用zookeeper的原因:
- NameServer是自己写的,方便扩展,去中心化,只要有一个NameServer在,整个注册中心环境就可以用。
- Zookeeper是CP的,在进行选举的时候,整个选举的时间太长,期间整个集群都处于不可用的状态,而这对于一个注册中心来说肯定是不能接受的。
- 用Zookeeper架构更复杂,部署需要占用单独的服务器。
Consumer获取消息的方式?
拉取式消费 Consumer主动从Broker中拉取消息,主动权由Consumer控制。一旦获取了批量消息,就会启动消费过程。不过,该方式的实时性较弱,即Broker中有了新的消息时消费者并不能及时发现并消费。
由于拉取时间间隔是由用户指定的,所以在设置该间隔时需要注意平稳:间隔太短,空请求比例会增加;间隔太长,消息的实时性太差
推送式消费(项目就采用这种模式) 该模式下Broker收到数据后会主动推送给Consumer。该获取方式一般实时性较高。 该获取方式是典型的发布-订阅模式,即Consumer向其关联的Queue注册了监听器,一旦发现有新的消息到来就会触发回调的执行,回调方法是Consumer去Queue中拉取消息。而这些都是基于Consumer与Broker间的长连接的。长连接的维护是需要消耗系统资源的。
pull:需要应用去实现对关联Queue的遍历,实时性差;但便于应用控制消息的拉取 push:封装了对关联Queue的遍历,实时性强,但会占用较多的系统资源
Consumer消费消息的模式?
广播模式:
- 广播消费模式下,相同Consumer Group的每个Consumer实例都接收同一个Topic的全量消息。即每条消息都会被发送到Consumer Group中的每个Consumer。
- 消费进度保存在consumer端。因为广播模式下consumer group中每个consumer都会消费所有消息,但它们的消费进度是不同。所以consumer各自保存各自的消费进度。
集群消费
- 集群消费模式下,相同Consumer Group的每个Consumer实例平均分摊同一个Topic的消息。即每条消息只会被发送到Consumer Group中的某个Consumer。
- 消费进度保存在broker中。consumer group中的所有consumer共同消费同一个Topic中的消息,同一条消息只会被消费一次。消费进度会参与到了消费的负载均衡中,故消费进度是需要共享的。
对比
| Messaging Product | Client SDK | Protocol and Specification | Ordered Message | Scheduled Message | Batched Message | BroadCast Message | Message Filter | Server Triggered Redelivery | Message Storage | Message Retroactive | Message Priority | High Availability and Failover | Message Track | Configuration | Management and Operation Tools |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| ActiveMQ | Java, .NET, C++ etc. | Push model, support OpenWire, STOMP, AMQP, MQTT, JMS | Exclusive Consumer or Exclusive Queues can ensure ordering | Supported | Not Supported | Supported | Supported | Not Supported | Supports very fast persistence using JDBC along with a high performance journal,such as levelDB, kahaDB | Supported | Supported | Supported, depending on storage,if using levelDB it requires a ZooKeeper server | Not Supported | The default configuration is low level, user need to optimize the configuration parameters | Supported |
| Kafka | Java, Scala etc. | Pull model, support TCP | Ensure ordering of messages within a partition | Not Supported | Supported, with async producer | Not Supported | Supported, you can use Kafka Streams to filter messages | Not Supported | High performance file storage | Supported offset indicate | Not Supported | Supported, requires a ZooKeeper server | Not Supported | Kafka uses key-value pairs format for configuration. These values can be supplied either from a file or programmatically. | Supported, use terminal command to expose core metrics |
| RocketMQ | Java, C++, Go | Pull model, support TCP, JMS, OpenMessaging | Ensure strict ordering of messages,and can scale out gracefully | Supported | Supported, with sync mode to avoid message loss | Supported | Supported, property filter expressions based on SQL92 | Supported | High performance and low latency file storage | Supported timestamp and offset two indicates | Not Supported | Supported, Master-Slave model, without another kit | Supported | Work out of box,user only need to pay attention to a few configurations | Supported, rich web and terminal command to expose core metrics |
RMQ延迟更低,支持延迟消息,同步刷盘不丢消息。
和Kafka异同
相同之处
- 两者均利用了操作系统Page Cache的机制,同时尽可能通过顺序io降低读写的随机性,将读写集中在很小的范围内,减少缺页中断,进而减少了对磁盘的访问,提高了性能。
- Kafka的Partition = RocketMq的Queue. 单个Queue是有序的,是分片。RocketMQ官方虽宣称支持严格有序,但方式为使用单个分区。
- 都支持事务。 RocketMQ支持事务消息,采用二阶段提交+broker定时回查。
- 都用了顺序存储、Page Cache、异步刷盘、零拷贝
- RocketMQ也有Consumer Group概念
不同之处
RocketMq支持延迟消息
存储形式
- Kafka采用partition,每个topic的每个partition对应一个文件。顺序写入,定时刷盘。但一旦单个broker的partition过多,则顺序写将退化为随机写,Page Cache脏页过多,频繁触发缺页中断,性能大幅下降。
- RocketMQ采用CommitLog+ConsumeQueue,单个broker所有topic在CommitLog中顺序写,Page Cache只需保持最新的页面即可。同时每个topic下的每个queue都有一个对应的ConsumeQueue文件作为索引。ConsumeQueue占用Page Cache极少,刷盘影响较小。
存储可靠性
- RocketMQ支持异步刷盘,同步刷盘,同步Replication,异步Replication。
- Kafka使用异步刷盘,异步Replication。这里的异步刷盘指pageCache更新到磁盘里,比如命令fsync
消息重复
- RocketMQ仅支持At Least Once。
- Kafka支持At Least Once、Exactly Once。
消息过滤
- RocketMQ执行过滤是在Broker端,支持tag过滤及自定义过滤逻辑。
- Kafka不支持Broker端的消息过滤,需要在消费端自定义实现。
消费
- RocketMQ支持按照时间回溯消费,实现原理与Kafka相同。
- Kafka需要先根据时间戳找到offset,然后从offset开始消费。
- RocketMQ相比Kafka多了个广播消费。广播消费模式下,相同Consumer Group的每个Consumer实例都接收同一个Topic的全量消息。即每条消息都会被发送到Consumer Group中的每个Consumer。消费进度保存在consumer端。因为广播模式下consumer group中每个consumer都会消费所有消息,但它们的消费进度是不同。所以consumer各自保存各自的消费进度。
服务发现
- RocketMQ自己实现了namesrv。
- Kafka使用ZooKeeper。
高可用
- RocketMQ在高可用设计上粒度只控制在Broker。其保证高可用是通过master-slave主从复制来解决的。
- Kafka控制高可用的粒度是放在分区上。每个topic的leader分区和replica分区都可以在所有broker上负载均衡的存储。
- Kafka的这种设计相比RocketMQ这种主从复制的设计有以下好处:
- Kafka中不需要设置从broker,所有的broker都可以收发消息。负载均衡也做的更好。
- Kafka的分区选举是自动做的,RocketMQ需要自己指定主从关系。
- Kafka分区的复制份数指定为N,则可以容忍N-1个节点的故障。发生故障只需要分区leader选举下即可,效率很高。
比kafka低延迟原因
1. 消息存储与磁盘访问的优化
- RocketMQ:RocketMQ 采用 顺序写入 和 顺序读取 的方式来存储消息,这种方式在磁盘 I/O 上比 Kafka 更加优化。RocketMQ 使用 内存映射文件(mmap)来将数据直接映射到内存中,而不是每次都需要通过文件系统的系统调用来访问磁盘,这大大减少了磁盘访问的延迟。
- Kafka:Kafka 同样使用顺序写入和日志文件存储,但它的存储系统依赖于操作系统的缓存和文件系统的管理,在高并发的情况下,尤其是在磁盘 I/O 压力较大时,可能会出现较大的延迟。
2. 消息发送和消费模型
- RocketMQ:RocketMQ 支持基于 Pull 和 Push 的消费模型。默认情况下,RocketMQ 在 Push 模式下处理消息,这使得消费者能在消息生产时就能快速获取消息,降低了消费的延迟。
- Kafka:Kafka 采用 Pull 模式,消费者需要不断地拉取消息。这种方式虽然能保证高吞吐量,但在某些情况下,尤其是消费端速率较慢时,可能会增加延迟。Kafka 需要保证消息不丢失并保持高吞吐量,可能在某些情况下会导致消息积压,从而影响延迟。
###