RocketMQ 是一个纯 Java、分布式、队列模型的开源消息中间件,是阿里参考 Kafka 特点研发的一个队列模型的消息中间件,后开源给 apache 基金会。
我之前写过 RabbitMQ 的文章,毕竟先入为主,后续在介绍 RocketMQ 的性能时,可能会交叉地拿 RabbitMQ 做比拟。以后公司内的消息中间件选型,也是从 RabbitMQ 转为了 RocketMQ,技术总监通知我的理由也很简略,因为 RocketMQ 的分布式集群可用性更高,运维更简略。
也确实,抛去为大数据而生的 Kafka 不说,这二者除了在架构和应用形式上差距很大,但在理论利用中的性能、成果上差距不大。有人说 RabbitMQ 的响应速度更快,有人说 RocketMQ 的数据吞吐量更高,但也是差距不大,各有千秋。国内也没有多少公司有那么大的体量,对性能那么较真。
1. 根本组件
1.1. 名词概念
Name Server
Name Server 是 RocketMQ 集群的协调者,集群的各个组件是通过 Name Server 获取各种属性和地址信息的。次要性能包含两局部:
- 各个 Broker 定期上报本人的状态信息到 Name Server,维持心跳。
- 各个客户端,包含 Producer、Consumer,以及命令行工具,通过 Name Server 获取 Broker 等最新的状态信息。
所以,在启动 Broker、生产者和消费者之前,必须通知它们 Name Server 的地址。为了进步可靠性,倡议启动多个 Name Server 组成集群,独自部署。因而在产线中,能够动静增减 Name Server 集群中节点的数量。
能够把 Name Server 类比成 Kafka 中的 ZooKeeper,那为什么不间接用 ZooKeeper 呢?因为 RocketMQ 只能用到 ZooKeeper 的少部分性能,间接用会显得太重,就本人开发了相较而言更轻量级、更满足本身个性的 Name Server。
Broker
Broker 次要负责音讯的存储、投递和查问以及服务高可用保障,说白了就是 RocketMQ 的服务器。Broker 是中间件的外围,相对不能挂,更是要保障它的可靠性,通常会搭建主从高可用架构,因而 Broker 有分 Master Broker(BrokerId 为 0)和 Slave Broker(BrokerId 非 0)。
每个 Broker 与 Name Server 集群中的所有节点建设长连贯,定时注册 Topic 信息到所有 Name Server。Broker 启动后须要实现一次将本人注册至 Name Server 的操作;随后每隔 30s 定期向 Name Server 上报 Topic 路由信息。
Name Server 集群节点互相不通信,所以上报信息时须要上报所有节点。另外 Name Server 是无状态的,即数据并不会做长久化存储,全副存储在内存中,重启后即隐没。
Producer
与 Name Server 集群中的其中一个节点(随机)建设长链接(Keep-alive),定期从 Name Server 读取 Topic 路由信息,并向提供 Topic 服务的 Master Broker 建设长链接,且定时向 Master Broker 发送心跳。
Consumer
与 Name Server 集群中的其中一个节点(随机)建设长连贯,定期从 Name Server 拉取 Topic 路由信息,并向提供 Topic 服务的 Master Broker、Slave Broker 建设长连贯,且定时向 Master Broker、Slave Broker 发送心跳。Consumer 既能够从 Master Broker 订阅音讯,也能够从 Slave Broker 订阅音讯,订阅规定由 Broker 配置决定。
Topic
音讯主题,一级音讯分类,通过 Topic 对不同的业务音讯进行分类。
Tag
音讯标签,用来进一步辨别某个 Topic 下的音讯分类,音讯从生产者收回即带上的属性。
Queue
对于 RocketMQ 来说,Topic 是逻辑上的概念,一个 Topic 能够散布在各个 Broker 上,把一个 Topic 散布在一个 Broker 上的子集定义为一个 Topic 分片。在分片根底上再等分为若干份(可指定份数)后的其中一份,就是 Queue 队列。
Queue 是负载平衡过程中资源分配的根本单元。
1.2. Consumer Group 和 Queue
Consumer Group 消费者组
在理论生产音讯时,都须要申明 Topic 名、消费者组名。在 集群生产模式 下,同一个 Topic 内的音讯,会别离分发给同一个 Consumer Group 内的不同 Consumer,以达到负载平衡的成果。
怎么了解同一个 Consumer Group 内的不同 Consumer 呢?不同业务零碎基于同一个 Consumer Group 生产同一个 Topic 内的音讯算是;为了进步并发量,间接将某个业务零碎的过程横向拓展(如:k8s 中减少几个 pod)也算是。
但如果只是在某个 Consumer 的代码中,减少几个线程,如 @RocketMQMessageListener.consumeThreadMax
,不算是减少 Consumer。只是作为一个 Consumer 在拉取了一批音讯后,减少线程去并发执行。
读、写队列
在创立、更改 Topic 时,会要求设置读队列数、写队列数。
在发送音讯时,会依据 Topic 写队列数返回路由信息;在生产音讯时,会依据 Topic 读队列数返回路由信息。
读、写队列并非物理上齐全对抗的队列,如:
- 写队列 8 个,读队列 4 个: 会创立 8 个文件夹(0、1、2、3、4、5、6、7),音讯会发送给这 8 个队列,但生产时只能生产到 4 个队列(0、1、2、3),另外 4 个队列(4、5、6、7)中的音讯不会被生产到。
- 写队列 4 个,读队列 8 个: 音讯只会发送给 4 个队列(0、1、2、3),生产时会从 8 个队列中生产音讯,但只有(0、1、2、3)队列中有音讯。如果某个消费者被调配了队列(4、5、6、7),则什么音讯也收不到。
这样来看,最好的形式是 读队列数 = 写队列数
,那 RocketMQ 为什么还要多此一举呢?为了不便队列扩容、缩容。
一个 topic 在每个 broker 上创立了 128 个队列,当初须要将队列缩容到 64 个,怎么做能力 100% 不会失落音讯,并且无需重启应用程序?最佳实际:先缩容写队列 128->64,写队列由 0 1 2 ……127 缩至 0 1 2 ……..63。等到 64 65 66……127 中的音讯全副生产完后,再缩容读队列 128->64。如果同时缩容写队列和读队列,可能会导致局部音讯未被生产。
Consumer Group 和 Queue 无关负载平衡
既然是负载平衡,那么探讨的就是集群生产模式。之前说过,Queue 是负载平衡过程中资源分配的根本单元。
因而,在一个 Consumer Group 内,Consumer 和 Queue 是 1:n 的关系:
- 一个 Queue 最多只能调配给一个 Consumer。
- 一个 Cosumer 能够调配失去多个 Queue。
也因而,Consumer 数量应该小于等于 Queue 数量。如果 Consumer 超过 Queue 数量,那么多余的 Consumer 将无奈生产音讯。
2. 高可用架构
后面介绍过 RocketMQ 各个组件的名词概念,那么当初说说,这些组件是如何搭建成 RocketMQ 的架构的。
2.1. Name Server 集群
是一个简直无状态节点,可集群部署,集群节点间互相独立没有信息替换。其性能次要为更新和发现 Broker 服务,生产者或消费者可能通过其查找到各主题相应的 Broker IP 列表
之前说过,Name Server 是独立的,每台 NameServer 都会有残缺的集群路由信息,包含所有的 Broker 节点的信息,咱们的数据信息等等。所以只有任何一台 NamseServer 存活下来,就能够保留 RocketMQ 信息的失常运行,不会呈现故障。
所以为了进步可用性,Name Server 的节点数至多是 2 个及以上。尽管能够间接部署在 Broker 所处的机器上,但如果有条件最好独自部署。
2.2. Broker 集群
能够搭建的 Broker 集群有很多种,依照功能性来分:
- 单节点模式:就一个 Master Broker。
- 主从模式 :每个 Master Broker 配多个 Slave Broker。只有 Master 承受 Topic 创立 Queue,音讯写入等,Slave 只是同步 Master 上的这些数据。不过有 同步 / 异步 之分。(1)同步,只有当音讯从 Master 同步到 Slave,才算音讯发送胜利;(2)异步,音讯发送到 Master 就算发送胜利,后续音讯在从 Master 异步刷新到 Slave 上。
- 多 Master 模式:能够单是多个 Master,也能够是多个 Master-Slave 主从,多个 Master 能够进步音讯并发性、高可用性,这也是为什么 Topic 会在各个 Master Broker 上创立分片队列。
- Dledger 模式:在主从模式中,Slave 挂掉了影响不大,可如果 Master 挂掉了就都不能用了。除非手动的将某个 Slave Broker 切换为新的 Master Broker。而 Dledger 就解决了这个问题,它能够监控一组 Broker,当 Master 挂掉后,会从余下的 Slave 中从新选举新的 Master。
综上所述,最完满的高可用集群架构是:多 Master,每个 Master 配置多个 Slave,并且所有主从 Broker 都启用了 Dledger。
看完这些,第一反馈就是这和 Redis 的高可用集群架构如同,后面多 Master 多 Slave 根本一样,而后 RocketMQ 的 Dledger 模式,不就对应 Redis 中的 Sentinel 哨兵模式嘛。看来分布式倒退到明天,对于高可用架构的计划逐步稳固且对立了。
!!!图
2.3. Dledger 模式
DLedger 是 OpenMessaging 中一个基于 Raft 算法的 CommitLog 存储库实现,从 RocketMQ 4.5.0 版本 开始,RocketMQ 引入 DLedger 模式来解决了 Broker 组内主动故障转移的问题。当初用于部署 RocketMQ 集群最常见的是用 RocketMQ-on-DLedger Group。
RocketMQ-on-DLedger Group 是指一组雷同名称的 Broker,至多须要 3 个节点,通过 Raft 主动选举出一个 Leader,其余节点 作为 Follower,并在 Leader 和 Follower 之间复制数据以保障高可用。
RocketMQ-on-DLedger Group 能主动容灾切换,并保证数据统一。
RocketMQ-on-DLedger Group 是能够程度扩大的,也即能够部署任意多个 RocketMQ-on-DLedger Group 同时对外提供服务。
在基于 RocketMQ-on-DLedger Group 部署时,每个 Broker 节点的配置文件须要多加一下配置,当然还是针对 RocketMQ 4.5.0 以上版本:
enableDLegerCommitLog:是否启动 DLedger
dLegerGroup:DLedger Raft Group 的名字,倡议和 brokerName 保持一致
dLegerPeers:DLedger Group 内各节点的地址与端口信息(同一个 Group 内的各个节点配置必须要保障统一)dLegerSelfId:节点 id, 必须属于 dLegerPeers 中的一个;同 Group 内各个节点要惟一,例如:第一个节点配置为”n0”,第二个节点配置为”n1”,第三个节点配置为”n2”sendMessageThreadPoolNums:发送线程个数(倡议配置成 CPU 核数)
2.4. 和 RabbitMQ 比照
结尾说,咱们的技术总监因为高可用集群而抉择了 RabbitMQ,那么能够回顾一下 RabbitMQ 的集群是什么样的。
一般集群
在多台机器上启动多个 RabbitMQ 实例,每个机器启动一个。创立的 Queue,只会放在一个 RabbitMQ 实例上,然而每个实例都同步 Queue 的元数据(元数据能够认为是 Queue 的一些配置信息,通过元数据,能够找到 Queue 所在实例)。生产的时候,实际上如果连贯到了另外一个实例,那么那个实例会从 Queue 所在实例上拉取数据过去。
问题在于,如果消费者读取 Queue 所在的不是所属实例,还要从原实例拉取数据,有性能开销。可如果恰好是 Queue 理论所属实例,那和单节点有啥区别,仍然有单节点的性能瓶颈。
而且这种集群模式,也只能进步了局部并发量,并没有高可用性。因为其余实例只同步了 Queue 的元数据,如果 Queue 所处实例宕机了,仍然拉取不到 Queue 中的音讯。
镜像集群
镜像集群是在前者的模式做了更改。创立的 Queue 还是只会放在一个实例上,但其余每个实例不光同步 Queue 的元数据,还同步音讯数据。
这样就有了高可用性,Queue 所属实例宕机了,未生产完的音讯仍然能够从其余实例中读到。
但这相当于每个实例上,都残缺保留了所有队列的音讯,不说性能,光对磁盘的要求有多大。
RocketMQ、RabbitMQ 比照
反观 RocketMQ 的高可用架构则更迷信:
- Topic 在不同 Master Broker 上分片,Queue 扩散在不同集群。尽管 RocketMQ 的主从同步相似于镜像,也是将 Queue 的元数据和音讯都同步过来。但毕竟只是通过拆分过的 Topic 局部数据,量没那么大。
- 读写拆散,想拓展写并发就拓展 Master,想拓展读并发就拓展 Slave,更加灵便。
- RocketMQ 每个实例各有分工,甚至还有独立的 Name Server,所以对单个集群实例的性能耗费不大。而对于 RabbitMQ 而言,每个实例可能会有性能瓶颈,对机器上的物理资源可能要求较高。
3. 其余
3.1. 内部插件
控制台 console
RocketMQ 不像 RabbitMQ 自带 console,不过它对外提供 API。目前社区有很多可接入 RocketMQ console 的前端我的项目,目前我用的是 rocketmq-console-ng
,还挺不错的。
接入 Prometheus
console 毕竟在监控无限,目前应用最宽泛监控解决方案的就是 Prometheus 了吧,RocketMQ 也提供接入的计划。
这里就提到配角 RocketMQ-Exporter
,目前已被 Prometheus 官网收录,其地址为 https://github.com/apache/roc…。它首先从 RocketMQ 集群采集数据,而后借助 Prometheus 提供的第三方客户端库将采集的数据规范化成合乎 Prometheus 零碎要求的数据,Prometheus 定时去从 Exporter 拉取数据即可。
3.2. RocketMQ、RabbitMQ 比照
搭建运维上
在运维搭建时,RabbitMQ 要简略的多,全程就一个 server,还蕴含了 console 控制台。
同样的状况在 RocketMQ 上则绝对简单,要别离搭建 Name Server、Broker(分 Master、Slave),要 console 也要本人搭。
但简单有简单的益处,在高可用集群上就体现进去了,这里不再赘言。
开发应用上
RabbitMQ 的 AMQP 协定,相较于 Kafka、RocketMQ 来说,学习老本要高的多。无论是发送一般音讯,还是简单一点的像提早音讯,都须要了解和应用交换机和队列之间的配合。而在 RocketMQ 上应用时则简略的多,无奈提供一个 API 即可,框架封装了很多实现的细节。
有人说 RocketMQ 框架自身较重,但重有重的益处,框架封装的越多,对于应用的人来说就越不便。像程序音讯、提早音讯、事务音讯等,简直能够开箱即用。最近在学车,感觉 RabbitMQ 像是手动挡,而 RocketMQ 像是自动挡,不晓得这个比喻是否贴切。