APP下载

分散式讯息伫列差异化总结 太全了

消息来源:baojiabao.com 作者: 发布时间:2024-05-18

报价宝综合消息分散式讯息伫列差异化总结 太全了

本文将对Kafka、RabbitMQ、ZeroMQ、RocketMQ、ActiveMQ从17 个方面综合对比作为讯息伫列使用时的差异。

一、资料文件

Kafka:资料数量中等。有Kafka作者自己写的书,网上资料也有一些。

RabbitMQ:资料数量多。有一些不错的书,网上资料多。

ZeroMQ:资料数量少。专门写ZeroMQ的书较少,网上的资料多是一些程式码的实现和简单介绍。

RocketMQ:资料数量少。专门写RocketMQ的书目前有了两本;网上的资料良莠不齐,官方文件很简洁,但是对技术细节没有过多的描述。

ActiveMQ:资料数量多。没有专门写ActiveMQ的书,网上资料多。

二、开发语言

Kafka:Scala

RabbitMQ:Erlang

ZeroMQ:C

RocketMQ:Java

ActiveMQ:Java

三、支援的协议

Kafka:自己定义的一套…(基于TCP)

RabbitMQ:AMQP

ZeroMQ:TCP、UDP

RocketMQ:自己定义的一套…

ActiveMQ:OpenWire、STOMP、REST、XMPP、AMQP

四、讯息储存

1、Kafka

内存、磁盘、数据库。支援大量堆积。

Kafka的最小储存单元是分割槽,一个topic包含多个分割槽,Kafka建立主题时,这些分割槽会被分配在多个服务器上,通常一个broker一台服务器。

分割槽首领会均匀地分布在不同的服务器上,分割槽副本也会均匀的分布在不同的服务器上,确保负载均衡和高可用性,当新的broker加入丛集的时候,部分副本会被移动到新的broker上。

根据配置档案中的目录清单,Kafka会把新的分割槽分配给目录清单里分割槽数最少的目录。

预设情况下,分割槽器使用轮询算法把讯息均衡地分布在同一个主题的不同分割槽中,对于传送时指定了key的情况,会根据key的hashcode取模后的值存到对应的分割槽中。

2、RabbitMQ

内存、磁盘。支援少量堆积。

RabbitMQ的讯息分为持久化的讯息和非持久化讯息,不管是持久化的讯息还是非持久化的讯息都可以写入到磁盘。

持久化的讯息在到达伫列时就写入到磁盘,并且如果可以,持久化的讯息也会在内存中储存一份备份,这样可以提高一定的效能,当内存吃紧的时候会从内存中清除。

非持久化的讯息一般只存在于内存中,在内存吃紧的时候会被换入到磁盘中,以节省内存。

引入映象伫列机制,可将重要伫列“复制”到丛集中的其他broker上,保证这些伫列的讯息不会丢失。

配置映象的伫列,都包含一个主节点master和多个从节点slave,如果master失效,加入时间最长的slave会被提升为新的master,除传送讯息外的所有动作都向master传送,然后由master将命令执行结果广播给各个slave,RabbitMQ会让master均匀地分布在不同的服务器上,而同一个伫列的slave也会均匀地分布在不同的服务器上,保证负载均衡和高可用性。

3、ZeroMQ

讯息传送端的内存或者磁盘中。不支援持久化。

4、RocketMQ

磁盘。支援大量堆积。

commitLog档案存放实际的讯息资料,每个commitLog上限是1G,满了之后会自动新建一个commitLog档案储存资料。

ConsumeQueue伫列只存放offset、size、tagcode,非常小,分布在多个broker上。

ConsumeQueue相当于CommitLog的索引档案,消费者消费时会从consumeQueue中查询讯息在commitLog中的offset,再去commitLog中查询元资料。

ConsumeQueue储存格式的特性,保证了写过程的顺序写盘(写CommitLog档案),大量资料IO都在顺序写同一个commitLog,满1G了再写新的。

加上RocketMQ是累计4K才强制从PageCache中刷到磁盘(快取),所以高并发写效能突出。

5、ActiveMQ

内存、磁盘、数据库。支援少量堆积。

五、讯息事务

Kafka:支援

RabbitMQ:支援。

客户端将通道设定为事务模式,只有当讯息被RabbitMQ接收,事务才能提交成功,否则在捕获异常后进行回滚。使用事务会使得效能有所下降

ZeroMQ:不支援

RocketMQ:支援

ActiveMQ:支援

六、负载均衡

1、Kafka

支援负载均衡。

1)一个broker通常就是一台服务器节点。

对于同一个Topic的不同分割槽,Kafka会尽力将这些分割槽分布到不同的Broker服务器上,zookeeper储存了broker、主题和分割槽的元资料资讯。

分割槽首领会处理来自客户端的生产请求,Kafka分割槽首领会被分配到不同的broker服务器上,让不同的broker服务器共同分担任务。

每一个broker都快取了元资料资讯,客户端可以从任意一个broker获取元资料资讯并快取起来,根据元资料资讯知道要往哪里传送请求。

2)Kafka的消费者组订阅同一个topic,会尽可能地使得每一个消费者分配到相同数量的分割槽,分摊负载。

3)当消费者加入或者退出消费者组的时候,还会触发再均衡,为每一个消费者重新分配分割槽,分摊负载。

Kafka的负载均衡大部分是自动完成的,分割槽的建立也是Kafka完成的,隐藏了很多细节,避免了繁琐的配置和人为疏忽造成的负载问题。

4)传送端由topic和key来决定讯息发往哪个分割槽,如果key为,那么会使用轮询算法将讯息均衡地传送到同一个topic的不同分割槽中。如果key不为,那么会根据key的hashcode取模计算出要发往的分割槽。

2、RabbitMQ

对负载均衡的支援不好。

1)讯息被投递到哪个伫列是由交换器和key决定的,交换器、路由键、伫列都需要手动建立。

RabbitMQ客户端传送讯息要和broker建立连线,需要事先知道broker上有哪些交换器,有哪些伫列。

通常要宣告要传送的目标伫列,如果没有目标伫列,会在broker上建立一个伫列,如果有,就什么都不处理,接着往这个伫列传送讯息。假设大部分繁重任务的伫列都建立在同一个broker上,那么这个broker的负载就会过大。

可以在上线前预先建立伫列,无需宣告要传送的伫列,但是传送时不会尝试建立伫列,可能出现找不到伫列的问题,RabbitMQ的备份交换器会把找不到伫列的讯息储存到一个专门的伫列中,以便以后查询使用。

使用映象伫列机制建立RabbitMQ丛集可以解决这个问题,形成master-slave的架构,master节点会均匀分布在不同的服务器上,让每一台服务器分摊负载。slave节点只是负责转发,在master失效时会选择加入时间最长的slave成为master。

当新节点加入映象伫列的时候,伫列中的讯息不会同步到新的slave中,除非呼叫同步命令,但是呼叫命令后,伫列会阻塞,不能在生产环境中呼叫同步命令。

2)当RabbitMQ伫列拥有多个消费者的时候,伫列收到的讯息将以轮询的分发方式传送给消费者。每条讯息只会传送给订阅列表里的一个消费者,不会重复。

这种方式非常适合扩充套件,而且是专门为并发程式设计的。

如果某些消费者的任务比较繁重,那么可以设定basicQos限制通道上消费者能保持的最大未确认讯息的数量,在达到上限时,RabbitMQ不再向这个消费者传送任何讯息。

3)对于RabbitMQ而言,客户端与丛集建立的TCP连线不是与丛集中所有的节点建立连线,而是挑选其中一个节点建立连线。

但是RabbitMQ丛集可以借助HAProxy、LVS技术,或者在客户端使用算法实现负载均衡,引入负载均衡之后,各个客户端的连线可以分摊到丛集的各个节点之中。

客户端均衡算法:

轮询法。按顺序返回下一个服务器的连线地址。

加权轮询法。给配置高、负载低的机器配置更高的权重,让其处理更多的请求;而配置低、负载高的机器,给其分配较低的权重,降低其系统负载。

随机法。随机选取一个服务器的连线地址。

加权随机法。按照概率随机选取连线地址。

源地址杂凑法。通过杂凑函式计算得到的一个数值,用该数值对服务器列表的大小进行取模运算。

最小连线数法。动态选择当前连线数最少的一台服务器的连线地址。

3、ZeroMQ

去中心化,不支援负载均衡。本身只是一个多执行绪网络库。

4、RocketMQ

支援负载均衡。

一个broker通常是一个服务器节点,broker分为master和slave,master和slave储存的资料一样,slave从master同步资料。

1)nameserver与每个丛集成员保持心跳,储存着Topic-Broker路由资讯,同一个topic的伫列会分布在不同的服务器上。

2)传送讯息通过轮询伫列的方式传送,每个伫列接收平均的讯息量。传送讯息指定topic、tags、keys,无法指定投递到哪个伫列(没有意义,丛集消费和广播消费跟讯息存放在哪个伫列没有关系)。

tags选填,类似于 Gmail 为每封邮件设定的标签,方便服务器过滤使用。目前只支 持每个讯息设定一个 tag,所以也可以类比为 Notify 的 MessageType 概念。

keys选填,代表这条讯息的业务关键词,服务器会根据 keys 建立杂凑索引,设定后, 可以在 Console 系统根据 Topic、Keys 来查询讯息,由于是杂凑索引,请尽可能 保证 key 唯一,例如订单号,商品 Id 等。

3)RocketMQ的负载均衡策略规定:

Consumer数量应该小于等于Queue数量,如果Consumer超过Queue数量,那么多余的Consumer 将不能消费讯息。

这一点和Kafka是一致的,RocketMQ会尽可能地为每一个Consumer分配相同数量的伫列,分摊负载。

5、ActiveMQ

支援负载均衡。可以基于zookeeper实现负载均衡。

七、丛集方式

1、Kafka

天然的‘Leader-Slave’无状态丛集,每台服务器既是Master也是Slave。

分割槽首领均匀地分布在不同的Kafka服务器上,分割槽副本也均匀地分布在不同的Kafka服务器上,所以每一台Kafka服务器既含有分割槽首领,同时又含有分割槽副本。

每一台Kafka服务器是某一台Kafka服务器的Slave,同时也是某一台Kafka服务器的leader。

Kafka的丛集依赖于zookeeper,zookeeper支援热扩充套件,所有的broker、消费者、分割槽都可以动态加入移除,而无需关闭服务,与不依靠zookeeper丛集的mq相比,这是最大的优势。

2、RabbitMQ

支援简单丛集,\'复制\'模式,对高阶丛集模式支援不好。

RabbitMQ的每一个节点,不管是单一节点系统或者是丛集中的一部分,要么是内存节点,要么是磁盘节点,丛集中至少要有一个是磁盘节点。

在RabbitMQ丛集中建立伫列,丛集只会在单个节点建立伫列程序和完整的伫列资讯(元资料、状态、内容),而不是在所有节点上建立。

引入映象伫列,可以避免单点故障,确保服务的可用性,但是需要人为地为某些重要的伫列配置映象。

3、ZeroMQ

去中心化,不支援丛集。

4、RocketMQ

常用 多对\'Master-Slave\' 模式,开源版本需手动切换Slave变成Master。

Name Server是一个几乎无状态节点,可丛集部署,节点之间无任何资讯同步。

Broker部署相对复杂,Broker分为Master与Slave,一个Master可以对应多个Slave,但是一个Slave只能对应一个Master。

Master与Slave的对应关系通过指定相同的BrokerName,不同的BrokerId来定义,BrokerId为0表示Master,非0表示Slave。

Master也可以部署多个。每个Broker与Name Server丛集中的所有节点建立长连线,定时注册Topic资讯到所有Name Server。

Producer与Name Server丛集中的其中一个节点(随机选择)建立长连线,定期从Name Server取Topic路由资讯,并向提供Topic服务的Master建立长连线,且定时向Master传送心跳。Producer完全无状态,可丛集部署。

Consumer与Name Server丛集中的其中一个节点(随机选择)建立长连线,定期从Name Server取Topic路由资讯,并向提供Topic服务的Master、Slave建立长连线,且定时向Master、Slave传送心跳。

Consumer既可以从Master订阅讯息,也可以从Slave订阅讯息,订阅规则由Broker配置决定。

客户端先找到NameServer, 然后通过NameServer再找到 Broker。

一个topic有多个伫列,这些伫列会均匀地分布在不同的broker服务器上。RocketMQ伫列的概念和Kafka的分割槽概念是基本一致的,Kafka同一个topic的分割槽尽可能地分布在不同的broker上,分割槽副本也会分布在不同的broker上。

RocketMQ丛集的slave会从master拉取资料备份,master分布在不同的broker上。

5、ActiveMQ

支援简单丛集模式,比如\'主-备\',对高阶丛集模式支援不好。

八、管理界面

Kafka:一般

RabbitMQ:

ZeroMQ:

RocketMQ:有管理后台, 但不是专案里自带, 需要自己启动一个单独的管理后台例项

ActiveMQ:一般

九、可用性

Kafka:非常高(分散式)

RabbitMQ:高(主从)

ZeroMQ:

RocketMQ:非常高(分散式)

ActiveMQ:高(主从)

十、讯息重复

Kafka:支援at least once、at most once

RabbitMQ:支援at least once、at most once

ZeroMQ:只有重传机制,但是没有持久化,讯息丢了重传也没有用。既不是at least once、也不是at most once、更不是exactly only once

RocketMQ:支援at least once

ActiveMQ:支援at least once

十一、吞吐量TPS

Kafka:极大

Kafka按批次传送讯息和消费讯息。传送端将多个小讯息合并,批量发向Broker,消费端每次取出一个批次的讯息批量处理。

RabbitMQ:比较大

ZeroMQ:极大

RocketMQ:

RocketMQ接收端可以批量消费讯息,可以配置每次消费的讯息数,但是传送端不是批量传送。

ActiveMQ:比较大

十二、订阅形式和讯息分发

1、Kafka

基于topic以及按照topic进行正则匹配的释出订阅模式。

1)传送

传送端由topic和key来决定讯息发往哪个分割槽,如果key为,那么会使用轮询算法将讯息均衡地传送到同一个topic的不同分割槽中。如果key不为,那么会根据key的hashcode取模计算出要发往的分割槽。

2)接收

consumer向群组协调器broker传送心跳来维持他们和群组的从属关系以及他们对分割槽的所有权关系,所有权关系一旦被分配就不会改变除非发生再均衡(比如有一个consumer加入或者离开consumer group),consumer只会从对应的分割槽读取讯息。

Kafka限制consumer个数要少于分割槽个数,每个讯息只会被同一个 Consumer Group的一个consumer消费(非广播)。

Kafka的 Consumer Group订阅同一个topic,会尽可能地使得每一个consumer分配到相同数量的分割槽,不同 Consumer Group订阅同一个主题相互独立,同一个讯息会被不同的 Consumer Group处理。

2、RabbitMQ

提供了4种:direct、topic、Headers和fanout。

1)传送

先要宣告一个伫列,这个伫列会被建立或者已经被建立,伫列是基本储存单元。

由exchange和key决定讯息储存在哪个伫列。

direct>传送到和bindingKey完全匹配的伫列。

topic>路由key是含有"."的字串,会发送到含有“*”、“#”进行模糊匹配的bingKey对应的伫列。

fanout>与key无关,会发送到所有和exchange系结的伫列

headers>与key无关,讯息内容的headers属性(一个键值对)和系结键值对完全匹配时,会发送到此伫列。此方式效能低一般不用

2)接收

RabbitMQ的伫列是基本储存单元,不再被分割槽或者分片,对于我们已经建立了的伫列,消费端要指定从哪一个伫列接收讯息。

当RabbitMQ伫列拥有多个消费者的时候,伫列收到的讯息将以轮询的分发方式传送给消费者。每条讯息只会传送给订阅列表里的一个消费者,不会重复。

这种方式非常适合扩充套件,而且是专门为并发程式设计的。

如果某些消费者的任务比较繁重,那么可以设定basicQos限制通道上消费者能保持的最大未确认讯息的数量,在达到上限时,RabbitMQ不再向这个消费者传送任何讯息。

3、ZeroMQ

点对点(p2p)。

4、RocketMQ

基于topic/messageTag以及按照讯息型别、属性进行正则匹配的释出订阅模式。

1)传送

传送讯息通过轮询伫列的方式传送,每个伫列接收平均的讯息量。传送讯息指定topic、tags、keys,无法指定投递到哪个伫列(没有意义,丛集消费和广播消费跟讯息存放在哪个伫列没有关系)。

tags选填,类似于 Gmail 为每封邮件设定的标签,方便服务器过滤使用。目前只支 持每个讯息设定一个 tag,所以也可以类比为 Notify 的 MessageType 概念。

keys选填,代表这条讯息的业务关键词,服务器会根据 keys 建立杂凑索引,设定后, 可以在 Console 系统根据 Topic、Keys 来查询讯息,由于是杂凑索引,请尽可能 保证 key 唯一,例如订单号,商品 Id 等。

2)接收

广播消费。一条讯息被多个Consumer消费,即使Consumer属于同一个ConsumerGroup,讯息也会被ConsumerGroup中的每个Consumer都消费一次。

丛集消费。一个 Consumer Group中的Consumer例项平均分摊消费讯息。例如某个Topic有 9 条讯息,其中一个Consumer Group有3个例项,那么每个例项只消费其中的 3 条讯息。即每一个伫列都把讯息轮流分发给每个consumer。

5、ActiveMQ

点对点(p2p)、广播(释出-订阅)

点对点模式,每个讯息只有1个消费者;

释出/订阅模式,每个讯息可以有多个消费者。

1)传送

点对点模式:先要指定一个伫列,这个伫列会被建立或者已经被建立。

释出/订阅模式:先要指定一个topic,这个topic会被建立或者已经被建立。

2)接收

点对点模式:对于已经建立了的伫列,消费端要指定从哪一个伫列接收讯息。

释出/订阅模式:对于已经建立了的topic,消费端要指定订阅哪一个topic的讯息。

十三、顺序讯息

Kafka:支援。

设定生产者的max.in.flight.requests.per.connection为1,可以保证讯息是按照发送顺序写入服务器的,即使发生了重试。

Kafka保证同一个分割槽里的讯息是有序的,但是这种有序分两种情况:

①key为,讯息逐个被写入不同主机的分割槽中,但是对于每个分割槽依然是有序的

②key不为 , 讯息被写入到同一个分割槽,这个分割槽的讯息都是有序。

RabbitMQ:不支援

ZeroMQ:不支援

RocketMQ:支援

ActiveMQ:不支援

十四、讯息确认

1、Kafka

支援。

1)传送方确认机制

ack=0,不管讯息是否成功写入分割槽

ack=1,讯息成功写入首领分割槽后,返回成功

ack=all,讯息成功写入所有分割槽后,返回成功。

2)接收方确认机制

自动或者手动提交分割槽偏移量,早期版本的Kafka偏移量是提交给Zookeeper的,这样使得zookeeper的压力比较大,更新版本的Kafka的偏移量是提交给Kafka服务器的,不再依赖于zookeeper群组,丛集的效能更加稳定。

2、RabbitMQ

支援。

1)传送方确认机制,讯息被投递到所有匹配的伫列后,返回成功。如果讯息和伫列是可持久化的,那么在写入磁盘后,返回成功。支援批量确认和异步确认。

2)接收方确认机制,设定autoAck为false,需要显式确认,设定autoAck为true,自动确认。

当autoAck为false的时候,RabbitMQ伫列会分成两部分,一部分是等待投递给consumer的讯息,一部分是已经投递但是没收到确认的讯息。

如果一直没有收到确认讯号,并且consumer已经断开连线,RabbitMQ会安排这个讯息重新进入伫列,投递给原来的消费者或者下一个消费者。

未确认的讯息不会有过期时间,如果一直没有确认,并且没有断开连线,RabbitMQ会一直等待,RabbitMQ允许一条讯息处理的时间可以很久很久。

3、ZeroMQ

支援。

4、RocketMQ

支援。

5、ActiveMQ

支援。

十五、讯息回溯

Kafka:支援指定分割槽offset位置的回溯

RabbitMQ:不支援

ZeroMQ:不支援

RocketMQ:支援指定时间点的回溯

ActiveMQ:不支援

十六、讯息重试

1、Kafka

不支援,但是可以实现。

Kafka支援指定分割槽offset位置的回溯,可以实现讯息重试。

2、RabbitMQ

不支援,但是可以利用讯息确认机制实现。

RabbitMQ接收方确认机制,设定autoAck为false。

当autoAck为false的时候,RabbitMQ伫列会分成两部分,一部分是等待投递给consumer的讯息,一部分是已经投递但是没收到确认的讯息。

如果一直没有收到确认讯号,并且consumer已经断开连线,RabbitMQ会安排这个讯息重新进入伫列,投递给原来的消费者或者下一个消费者。

3、ZeroMQ

不支援。

4、RocketMQ

支援。

讯息消费失败的大部分场景下,立即重试99%都会失败,所以RocketMQ的策略是在消费失败时定时重试,每次时间间隔相同。

1)传送端的 send 方法本身支援内部重试,重试逻辑如下:

至多重试3次;

如果传送失败,则轮转到下一个broker;

这个方法的总耗时不超过sendMsgTimeout 设定的值,预设 10s,超过时间不在重试。

2)接收端。

Consumer 消费讯息失败后,要提供一种重试机制,令讯息再消费一次。Consumer 消费讯息失败通常可以分为以下两种情况:

由于讯息本身的原因,例如反序列化失败,讯息资料本身无法处理(例如话费充值,当前讯息的手机号被登出,无法充值)等。定时重试机制,比如过 10s 秒后再重试。

由于依赖的下游应用服务不可用,例如 db 连线不可用,外系统网络不可达等。即使跳过当前失败的讯息,消费其他讯息同样也会报错。这种情况可以 sleep 30s,再消费下一条讯息,减轻 Broker 重试讯息的压力。

5、ActiveMQ

不支援。

十七、并发度

1、Kafka

并发度高。

一个执行绪一个消费者,Kafka限制消费者的个数要小于等于分割槽数,如果要提高并行度,可以在消费者中再开启多执行绪,或者增加consumer例项数量。

2、RabbitMQ

并发度极高。本身是用Erlang语言写的,并发效能高。

可在消费者中开启多执行绪,最常用的做法是一个channel对应一个消费者,每一个执行绪把持一个channel,多个执行绪复用connection的tcp连线,减少效能开销。

当RabbitMQ伫列拥有多个消费者的时候,伫列收到的讯息将以轮询的分发方式传送给消费者。每条讯息只会传送给订阅列表里的一个消费者,不会重复。

这种方式非常适合扩充套件,而且是专门为并发程式设计的。

如果某些消费者的任务比较繁重,那么可以设定basicQos限制通道上消费者能保持的最大未确认讯息的数量,在达到上限时,RabbitMQ不再向这个消费者传送任何讯息。

3、ZeroMQ

并发度高。

4、RocketMQ

并发度高。

1)RocketMQ限制消费者的个数少于等于伫列数,但是可以在消费者中再开启多执行绪,这一点和Kafka是一致的,提高并行度的方法相同。

修改消费并行度方法

同一个 ConsumerGroup 下,通过增加 Consumer 例项数量来提高并行度,超过订阅伫列数的 Consumer例项无效。

提高单个 Consumer 的消费并行执行绪,通过修改引数consumeThreadMin、consumeThreadMax

2)同一个网络连线connection,客户端多个执行绪可以同时传送请求,连线会被复用,减少效能开销。

5、ActiveMQ

并发度高。

单个ActiveMQ的接收和消费讯息的速度在1万笔/秒(持久化 一般为1-2万, 非持久化 2 万以上),在生产环境中部署10个ActiveMQ就能达到10万笔/秒以上的效能,部署越多的ActiveMQ broker 在MQ上latency也就越低,系统吞吐量也就越高。

作者:28cm不含头

来源:https://www.zhihu.com/question/43557507

dbaplus社群欢迎广大技术人员投稿,投稿邮箱:[email protected]

对大资料技术运用尚不熟练?

想玩转热点技术,同时把握先机?

不妨来DAMS学点独家技能,

找到最适用的进阶法宝~

↓↓扫码可了解更多详情及报名↓↓

2019 DAMS中国资料智慧管理峰会-上海站

2019-12-31 08:50:00

相关文章