目录
概述
项目中消息中间件的选型
一、消息中间件概述
二、消息中间件区别及定义速览表
三、存储方式概述
四、负载均衡概述
五、集群方式概述
六、消息的订阅与发布概述
七、消息确认机制概述
八、消息重试概述
九、消息中间件并发度概述
十、管理界面友好程度
概述
在进行大型的、复杂的项目建设中,往往会涉及模块与模块之前的消息通信的问题,消息通信可以通过硬件设施或者软件设施来实现,通过硬件设施进行消息通信的就涉及比较高度安全和高度机密的政府及军用的通信了(通信成本高、时间长等特点),一般大、中、小型企业系统模块之前的通信都是通过消息中间件来实现的,如果再往高出走一点,就是企业自己根据业务需求,自己写一套复合自生业务发展的消息通信中间见咯。
本文将从 Kafka、RabbitMQ、ZeroMQ、RocketMQ、ActiveMQ这几个常见的消息中间件中进行选型对比。
项目中消息中间件的选型
Kafka
Kafka 是由 Apache软件基金会开发的一个开源流处理平台,由 Scala语言和 Java语言编写。Kafka是一种高吞吐量的分布式发布订阅消息系统,它可以处理消费者在网站中的所有动作流数据。 这种动作(网页浏览,搜索和其他用户的行动)是在现代网络上的许多社会功能的一个关键因素。 这些数据通常是由于吞吐量的要求而通过处理日志和日志聚合来解决。 对于像Hadoop一样的日志数据和离线分析系统,但又要求实时处理的限制,这是一个可行的解决方案。Kafka的目的是通过Hadoop的并行加载机制来统一线上和离线的消息处理,也是为了通过集群来提供实时的消息。
官网:http://kafka.apache.org/
RabbitMQ
RabbitMQ是实现了高级消息队列协议(AMQP)的开源消息代理软件(亦称面向消息的中间件)。RabbitMQ服务器是用Erlang语言编写的,而集群和故障转移是构建在开放电信平台框架上的。所有主要的编程语言均有与代理接口通讯的客户端库。
官网:https://www.rabbitmq.com/
ZeroMQ
ZeroMQ(简称ZMQ)是一个基于消息队列的多线程网络库,是一个非常轻量级的消息中间件,其对套接字类型、连接处理、帧、甚至路由的底层细节进行抽象,提供跨越多种传输协议的套接字。
ZMQ是网络通信中新的一层,介于应用层和传输层之间(按照TCP/IP划分),ZMQ是一个可伸缩层,可并行运行,分散在分布式系统间。
ZMQ不是单独的服务,而是一个嵌入式库,它封装了网络通信、消息队列、线程调度等功能,向上层提供简洁的API,应用程序通过加载库文件,调用API函数来实现高性能网络通信。
官网:http://api.zeromq.org/
RocketMQ
RocketMQ是一款分布式、队列模型的消息中间件,是阿里巴巴集团自主研发的专业消息中间件,借鉴参考了 JMS规范的MQ实现,参考了优秀的开源消息中间件Kafka,实现了业务削峰,分布式事务的优秀框架。
官网:http://rocketmq.apache.org/
ActiveMQ
ActiveMQ是 Apache软件基金会研发的开源消息中间件;由于 ActiveMQ是一个纯Java程序,因此只需要操作系统支持 Java虚拟机,ActiveMQ便可执行。
官网:http://activemq.apache.org/
功能 | Kafka | RabbitMQ | ZeroMQ | RocketMQ | ActiveMQ |
---|---|---|---|---|---|
支持的协议 | 自己定义的一套(基于TCP的协议) | AMQP | TCP/UDP | 自己定义的一套 | OpenWire、STOMP、REST、XMPP、AMQP |
存储方式 | 内存、磁盘、数据库。支持大量堆积。 | 内存、磁盘。支持少量堆积。 | 消息发送端的内存或者磁盘中。不支持持久化。 | 磁盘。支持大量堆积。 | 内存、磁盘、数据库。支持少量堆积。 |
事务支持 | 支持 | 支持,使用事务会使性能有所下降 | 不支持 | 支持 | 支持 |
负载均衡 | 支持负载均衡 | 对负载均衡的支持不好 | 去中心化,不支持负载均衡,本身只是一个多线程网络库 | 支持负载均衡 | 支持负载均衡,需要通过集成zookeeper来实现负载均衡 |
集群 | 天然的 Leader-Slave 无状态集群,每台服务器既是Master也是Slave。 | 支持简单集群,复制模式,对高级集群模式支持不好。 | 去中心化,不支持集群。 | 常用多对 Master-Slave 模式,开源版本需手动切换Slave变成Master。集群的slave会从master拉取数据备份,master分布在不同的broker上。 | 支持简单集群模式,比如'主-备',对高级集群模式支持不好。 |
高可用 | 非常高(分布式) | 高(主从) | 高 | 非常高(分布式) | 高(主从) |
消息重复 | 支持at least once、at most once | 支持at least once、at most once | 既不支持at least once、也不支持at most once、更不支持exactly only once | 支持at least once | 支持at least once |
吞吐量 (TPS) |
灰常大,Kafka 按批次发送消息和消费消息 | 比较大 | 非常大 | 大,rocketMQ 接收端可以批量消费消息,可以配置每次消费的消息数,但是发送端不是批量发送。 | 比较大 |
消息订阅与发布 | 基于topic以及按照topic进行正则匹配的发布订阅模式 | 提供了4种:direct, topic ,Headers和fanout | 点对点(P2P) | 基于topic/messageTag以及按照消息类型、属性进行正则匹配的发布订阅模式 | 点对点(P2P)、广播(发布-订阅) |
消息顺序支持 | 支持 | 不支持 | 不支持 | 支持 | 不支持 |
消息确认支持 | 支持 | 支持 | 支持 | 支持 | 支持 |
消息回溯支持 | 支持指定分区offset位置的回溯 | 不支持 | 不支持 | 支持指定时间点的回溯 | 不支持 |
消息重试支持 | 不支持,但是可以实现 | 不支持,但是可以利用消息确认机制实现 | 不支持 | 支持 | 不支持 |
并发度 | 高 | 极高 | 高 | 高 | 高 |
管理界面 | 一般 | 好 | 无 | 无 | 一般 |
Kafka
内存、磁盘、数据库。支持大量堆积。
Kafka 的最小存储单元是分区,一个 topic 包含多个分区,Kafka 创建 topic时,这些分区会被分配在多个服务器上,通常一个broker 一台服务器。 分区首领会均匀地分布在不同的服务器上,分区副本也会均匀的分布在不同的服务器上,确保负载均衡和高可用性,当新的 broker 加入集群的时候,部分副本会被移动到新的broker上。 根据配置文件中的目录清单,Kafka 会把新的分区分配给目录清单里分区数最少的目录。 默认情况下,分区通过使用轮询算法把消息均衡地分布在同一个 topic的不同分区中,对于发送时指定了 key的情况,会根据key的 hashcode取模后的值存到对应的分区中。
RabbitMQ
内存、磁盘。支持少量堆积。
RabbitMQ 的消息分为持久化的消息和非持久化消息,不管是持久化的消息还是非持久化的消息都可以写入到磁盘。 持久化的消息在到达队列时就写入到磁盘,如果可以,持久化的消息也会在内存中保存一份备份,这样可以提高一定的性能,当内存吃紧的时候会从内存中清除。非持久化的消息一般只存在于内存中,在内存吃紧的时候会被换入到磁盘中,以节省内存。
引入镜像队列机制,可将重要队列“复制”到集群中的其他 broker上,保证这些队列的消息不会丢失。配置镜像的队列,都包含一个主节点 master和多个从节点 slave,如果 master失效,加入时间最长的 slave会被提升为新的 master,除发送消息外的所有动作都向 master发送,然后由 master将命令执行结果广播给各个 slave,RabbitMQ 会让 master均匀地分布在不同的服务器上,而同一个队列的 slave也会均匀地分布在不同的服务器上,保证负载均衡和高可用性。
ZeroMQ
消息发送端的内存或者磁盘中,不支持持久化。
RocketMQ
磁盘。支持大量堆积。
commitLog 文件存放实际的消息数据,每个 commitLog上限是1G,满了之后会自动新建一个 commitLog文件保存数据。ConsumeQueue队列只存放offset、size、tagcode,非常小,分布在多个broker上。ConsumeQueue 相当于 CommitLog的索引文件,消费者消费时会从 consumeQueue中查找消息在 commitLog中的offset,再去 commitLog中查找元数据。
ConsumeQueue存储格式的特性,保证了写过程的顺序写盘(写CommitLog文件),大量数据IO都在顺序写同一个commitLog,满1G了再写新的。加上 RocketMQ是累计4K才强制从 PageCache中刷到磁盘(缓存),所以高并发写性能突出。
ActiveMQ
内存、磁盘、数据库。支持少量堆积。
Kafka
支持负载均衡。
1、一个 broker通常就是一台服务器节点。对于同一个 Topic的不同分区,Kafka会尽力将这些分区分布到不同的 Broker服务器上,zookeeper 保存了broker、Topic和分区的元数据信息。分区 Leader会处理来自客户端的生产请求,kafka分区 Leader会被分配到不同的 broker服务器上,让不同的 broker服务器共同分担任务。每一个 broker都缓存了元数据信息,客户端可以从任意一个 broker获取元数据信息并缓存起来,根据元数据信息知道要往哪里发送请求。
2、kafka的消费者组订阅同一个Topic,会尽可能地使得每一个消费者分配到相同数量的分区,分摊负载。
3、当消费者加入或者退出消费者组的时候,还会触发再均衡,为每一个消费者重新分配分区,分摊负载。kafka的负载均衡大部分是自动完成的,分区的创建也是自动完成的,隐藏了很多细节,避免了繁琐的配置和人为疏忽造成的负载问题。
4、发送端由 Topic和 Key来决定消息发往哪个分区,如果 Key为null,那么会使用轮询算法将消息均衡地发送到同一个 Topic的不同分区中。如果 Key不为null,那么会根据 Key的hashcode取模计算出要发往的分区。
RabbitMQ
对负载均衡的支持不好。
1、消息被投递到哪个队列是由 exchang和 key决定的,exchang、key、queue都需要手动创建。
RabbitMQ客户端发送消息要和 broker建立连接,需要事先知道 broker上有哪些 exchang,有哪些 queue。通常要声明要发送的目标 queue,如果没有目标 queue,会在 broker上创建一个 queue,如果有,就什么都不处理,接着往这个 queue中发送消息。假设大部分繁重任务的 queue都创建在同一个broker上,那么这个 broker的负载就会过大。(可以在上线前预先创建队列,无需声明要发送的队列,但是发送时不会尝试创建队列,可能出现找不到队列的问题,RabbitMQ 的备份 exchang会把找不到队列的消息保存到一个专门的队列中,以便以后查询使用)
使用镜像队列机制建立 RabbitMQ集群可以解决 broker的负载就会过大的问题,形成 master-slave的架构,master节点会均匀分布在不同的服务器上,让每一台服务器分摊负载。slave节点只是负责转发,在 master失效时会选择加入时间最长的 slave成为master。
当新节点加入镜像队列的时候,队列中的消息不会同步到新的slave中,除非调用同步命令,但是调用命令后,队列会阻塞,不能在生产环境中调用同步命令。
2、当 RabbitMQ队列拥有多个消费者的时候,队列收到的消息将以轮询的分发方式发送给消费者。每条消息只会发送给订阅列表里的一个消费者,不会重复。这种方式非常适合扩展,而且是专门为并发程序设计的。如果某些消费者的任务比较繁重,那么可以设置 basicQos限制信道上消费者能保持的最大未确认消息的数量,在达到上限时,RabbitMQ不再向这个消费者发送任何消息。
3、对于 RabbitMQ而言,客户端与集群建立的 TCP连接不是与集群中所有的节点建立连接,而是挑选其中一个节点建立连接。但是 RabbitMQ集群可以借助 HAProxy、LVS技术,或者在客户端使用算法实现负载均衡,引入负载均衡之后,各个客户端的连接可以分摊到集群的各个节点之中。
RabbitMQ Client均衡算法:
ZeroMQ
去中心化,不支持负载均衡。本身只是一个多线程网络库。
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唯一。
3、RocketMQ的负载均衡策略规定:Consumer数量应该小于等于 Queue数量,如果 Consumer超过 Queue数量,那么多余的 Consumer 将不能消费消息。这一点和 Kafka是一致的,RocketMQ会尽可能地为每一个 Consumer分配相同数量的队列,分摊负载。
ActiveMQ
支持负载均衡。基于 zookeeper实现负载均衡。
Kafka
天然的 Leader-Slave 无状态集群,每台服务器既是Master 也是Slave。
分区首领均匀地分布在不同的kafka服务器上,分区副本也均匀地分布在不同的kafka服务器上,所以每一台kafka服务器既含有分区首领,同时又含有分区副本,每一台kafka服务器是某一台kafka服务器的Slave,同时也是某一台kafka服务器的leader。
kafka的集群依赖于zookeeper,zookeeper支持热扩展,所有的broker、消费者、分区都可以动态加入移除,而无需关闭服务,与不依靠zookeeper集群的mq相比,这是最大的优势。但是现在最新的 kafka版本已经有了自己的一套zk,如果在项目中使用kafka时,如果没有硬性的要求,可以使用kafka中的zk,功能和目的都是一样的。
RabbitMQ
支持简单集群,复制模式,对高级集群模式支持不好。
rabbitmq的每一个节点,不管是单一节点系统或者是集群中的一部分,要么是内存节点,要么是磁盘节点,集群中至少要有一个是磁盘节点。
在rabbitmq集群中创建队列,集群只会在单个节点创建队列进程和完整的队列信息(元数据、状态、内容),而不是在所有节点上创建。
引入镜像队列,可以避免单点故障,确保服务的可用性,但是需要人为地为某些重要的队列配置镜像。
ZeroMQ
去中心化,不支持集群。
RocketMQ
常用多对 Master-Slave 模式,开源版本需手动切换Slave变成Master。集群的slave会从master拉取数据备份,master分布在不同的broker上。
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上。
ActiveMQ
支持简单集群模式,比如'主-备',对高级集群模式支持不好。
Kafka
Kafka是基于 topic以及按照 topic进行正则匹配的发布订阅模式。
消息发送:
发送端由 topic和 key来决定消息发往哪个分区,如果 key为null,那么会使用轮询算法将消息均衡地发送到同一个 topic的不同分区中。如果 key不为null,那么会根据 key的 hashcode取模计算出要发往的分区。
消息接收:
1、consumer向组协调器 broker发送心跳来维持和组的从属关系以及对分区的所有权关系,所有权关系一旦被分配就不会改变,除非发生再均衡(比如有一个consumer加入或者离开consumer group),consumer只会从对应的分区读取消息;
2、kafka限制 consumer个数要少于分区个数,每个消息只会被同一个 consumer group的一个consumer消费(非广播);
3、kafka的 consumer group订阅同一个topic,会尽可能地使得每一个 consumer分配到相同数量的分区,不同 consumer group订阅同一个 topic相互独立,同一个消息会被不同的 consumer group处理;
RabbitMQ
RabbitMQ提供了4种模式,即:direct、topic 、Headers和fanout。
消息发送:
首先,声明一个队列(新创建或已经存在的队列),队列是RabbitMQ基本存储单元,由 exchange和 key决定消息存储放在哪个队列中。
消息接收:
RabbitMQ 的队列是基本存储单元,不再被分区或者分片,对于已经创建了的队列,消费端要指定从哪一个队列接收消息。
当 RabbitMQ队列拥有多个消费者的时候,队列收到的消息将以轮询的方式分发给消费者。每条消息只会发送给订阅列表里的一个消费者,不会重复发送,这种方式非常适合扩展,而且是专门为并发程序设计的。
如果某些消费者的任务比较繁重,那么可以设置 basicQos限制信道上消费者能保持的最大未确认消息的数量,在达到上限时,RabbitMQ不再向这个消费者发送任何消息。
ZeroMQ
ZeroMQ的消息发布与订阅是点对点(p2p)的方式。
RocketMQ
RocketMQ是基于topic、messageTag以及按照消息类型、属性进行正则匹配的发布订阅模式。
消息发送:
发送消息通过轮询队列的方式发送,每个队列接收平均的消息量。发送消息需要依赖于topic、tags、keys,无法指定投递到哪个具体的队列,指定了也没有意义,集群消费和广播消费跟消息存放在哪个队列没有任何关系。
tags可以不指定,tag类似于 Gmail为每封邮件设置的标签,方便服务器过滤使用。目前只支持每个消息设置一个 tag,所以也可以类比为 Notify 的 MessageType 概念。
keys可以不指定,key代表这条消息的业务关键词,服务器会根据 keys创建哈希索引,设置后,可以在 Console系统根据 Topic、Keys 来查询消息,由于是哈希索引,所以需要尽可能保证 key唯一性。
消息接收:
1、广播消费,一条消息被多个 consumer消费,即使 consumer属于同一个 consumer group,消息也会被 consumer group中的每个 consumer都消费一次。
2、集群消费,一个 consumer group中的 consumer实例平均分摊消费消息。例如某个topic有 9 条消息,其中一个 consumer group有3个实例,那么每个实例只消费其中的 3 条消息。即每一个队列都把消息轮流分发给每个 consumer。
ActiveMQ
ActiveMQ是点对点(p2p)、广播(发布-订阅)的发布订阅模式。点对点模式,即每个消息对应1个消费者;发布/订阅模式,每个消息对应多个消费者。
消息发送:
消息接收:
Kafka
Kafka支持消息确认,Kafka的消息确认分为两方面,一个是消息发送方确认,一个是消息接收方确认:
1、发送方确认机制
2、接收方确认机制
通过自动或者手动提交分区偏移量来实现,早期版本的 Kafka偏移量是提交给 Zookeeper的,这样使得 Zookeeper的压力比较大,现在新版本的 Kafka的偏移量都是提交给 Kafka服务器端的,不再依赖于 Zookeeper,这样以来 Kafka集群的性能会更加稳定。
RabbitMQ
RabbitMQ支持消息确认,RabbitMQ的消息确认分为两方面,一个是消息发送方确认,一个是消息接收方确认:
1、发送方确认。消息被投递到所有匹配的队列后,返回成功。如果消息和队列是可持久化的,那么在写入磁盘后,返回成功。支持批量确认和异步确认。
2、接收方确认机制,设置autoAck为false,需要显式确认,设置autoAck为true,自动确认。
当autoAck为false的时候,rabbitmq队列会分成两部分,一部分是等待投递给consumer的消息,一部分是已经投递但是没收到确认的消息。如果一直没有收到确认信号,并且consumer已经断开连接,rabbitmq会安排这个消息重新进入队列,投递给原来的消费者或者下一个消费者。
未确认的消息不会有过期时间,如果一直没有确认,并且没有断开连接,rabbitmq会一直等待,rabbitmq允许一条消息处理的时间可以很久很久。
ZeroMQ
ZeroMQ支持消息确认。
RocketMQ
RocketMQ支持消息确认。
ActiveMQ
ActiveMQ支持消息确认。
Kafka:Kafka不支持消息重试,但是可以实现。kafka支持指定分区 offset位置的回溯,可以实现消息重试。
RabbitMQ:RabbitMQ不支持消息重试,但是可以利用消息确认机制实现。RabbitMQ接收方确认机制,设置 autoAck为false来实现。当 autoAck设为false的时候,RabbitMQ队列会分成两部分,一部分是等待投递给 consumer的消息,一部分是已经投递但是没收到确认的消息。如果一直没有收到确认信号,并且 consumer已经断开连接,RabbitMQ会安排这个消息重新进入队列,投递给原来的消费者或者下一个消费者。
ZeroMQ:ZeroMQ不支持消息重试。
RocketMQ:RocketMQ支持消息重试。消息消费失败的大部分场景下,立即重试99%都会失败,所以RocketMQ的策略是在消费失败时定时重试,每次时间间隔相同。
1、发送端的 send方法本身支持内部重试,重试逻辑如下:
2、接收端的 Consumer 消费消息失败后,要提供一种重试机制,使消息再消费一次。Consumer 消费消息失败通常可以分为以下两种情况:
ActiveMQ:ActiveMQ不支持消息重试。
Kafka
高,一个线程一个消费者,kafka限制消费者的个数要小于等于分区数,如果要提高并行度,可以在消费者中再开启多线程,或者增加consumer实例数量。
RabbitMQ
极高,RabbitMQ本身是用 Erlang语言写的,并发性能高。
可在消费者中开启多线程,最常见的做法是一个 channel对应一个消费者,每一个线程对应一个 channel,多个线程复用 connection的tcp连接,减少性能开销。
当 RabbitMQ队列拥有多个消费者的时候,队列收到的消息将以轮询的方式发送给消费者。每条消息只会发送给订阅列表里的一个消费者,不会重复发送。
如果某些消费者的任务比较繁重,那么可以设置 basicQos限制信道上消费者能保持的最大未确认消息的数量,在达到上限时,RabbitMQ不再向这个消费者发送任何消息。
ZeroMQ
ZeroMQ的并发度高。
RocketMQ
RocketMQ的并发度高RocketMQ限制消费者的个数少于等于队列数,但是可以在消费者中再开启多线程,这一点和 kafka是一致的,提高并行度的方法相同。
同一个网络连接时,客户端多个线程可以同时发送请求,连接会被复用,减少系统性能开销。
如何修改RocketMQ消费的并发度:
ActiveMQ
ActiveMQ并发度高。单个 ActiveMQ的接收和消费消息的速度在1万/秒(持久化一般为1-2万,非持久化2万以上),也就是说,如果部署 10个ActiveMQ就能达到10万/秒以上的并发度,部署越多的 ActiveMQ broker 在 MQ上 latency也就越低,系统吞吐量也就越高。
Kafka:一般
RabbitMQ:好
ZeroMQ:无
RocketMQ:无
ActiveMQ:一般
好了,关于 项目中如何选择消息中间件?消息中间件的选型? 就写到这儿了,如果还有什么疑问或遇到什么问题欢迎扫码提问,也可以给我留言哦,我会一一详细的解答的。
歇后语:“ 共同学习,共同进步 ”,也希望大家多多关注CSND的IT社区。
作 者: | 华 仔 |
联系作者: | [email protected] |
来 源: | CSDN (Chinese Software Developer Network) |
原 文: | https://blog.csdn.net/Hello_World_QWP/article/details/103628314 |
版权声明: | 本文为博主原创文章,请在转载时务必注明博文出处! |