主流消息中间件对比(以Kafka和RabbitMQ为主)

消息中间件对比

本文以RabbitMQKafka为主,对消息中间件的不同方面进行对比,整理了消息中间件在设计和实现方面的差别,在实际使用中可作参考、借鉴。
主要参考资料:
RabbitMQ、Kafka、RocketMQ官方文档;
分布式消息中间件时间(倪炜 著);
深入理解Kafka:核心设计与实践原理;
RabbitMQ实战指南(朱忠华 著);

支持的协议

Kafka 基于TCP,自己定义的协议
RabbitMQ AMQP
RocketMQ 自己定义的协议

开发语言

Kafka Scala
RabbitMQ Erlang
RocketMQ Java

架构

Kafka架构:
主流消息中间件对比(以Kafka和RabbitMQ为主)_第1张图片
RabbitMQ架构:
主流消息中间件对比(以Kafka和RabbitMQ为主)_第2张图片

消息存储

对比1:
Kafka中消息可存储于内存、磁盘、数据库。支持大量堆积。
RabbitMQ中消息存储与内存和磁盘。不支持大量堆积。(除了带有持久存储标签的消息,在内存够用的情况下一般不往磁盘中放,因此限制了消息的可堆积量)
对比2:
Kafka中的消息队列是以文件为存储形式,可以记录一个个偏移量offset,为很多机制的实现提供了诸多便利。
RabbitMQ中的消息队列是队列的形式,在很多方便没有kafka方便,这也是rabbitMQ不支持消息大量堆积的一个原因。

安全性保障(单节点故障应对)

Kafka:多副本机制
Kafka为分区引入了多副本的机制(Replica),通过增加副本数量可以提高容灾能力。同一分区的不同副本中保存的是相同的消息(在同一时刻,副本之间并非完全一样),副本之间是“一主多从”的关系,其中leader副本负责处理读写请求,follower副本只负责与leader副本的消息同步。

RabbitMQ:配置镜像队列
RabbitMQ引入镜像队列的机制,可以将队列镜像到集群中的其他broker节点上,如果集群中一个节点失效了,队列能自动切换到镜像队列的另一个节点上以保证服务的可用性。

相同点:主节点对外提供服务,从节点负责同步数据。即使在RabbitMQ中消费者可以与从节点建立连接进行订阅消费,其实质上也是从主节点获取消息。
不同点:两者主节点到从节点的复制原理不同。RabbitMQ属于同步复制,可以保证更强的一致性,如果出现网络波动或者网络故障等异常情况,那么整个数据链的性能就会大大降低。

Kafka的复制机制既不是完全的同步机制,也不是单纯的异步机制。

注释:事实上,同步复制要求所有能工作的follower副本都复制完这条消息才会被确认为已经成功提交,这种复制方式极大地影响了性能。而在异步复制方式下,follower副本异地从leader副本中复制数据,数据只要被leader副本写入就被认为已经成功提交。在这种情况下,如果follower副本都还没有复制完而落后于leader副本,突然leader副本宕机则会导致数据丢失。Kafka使用的复制方式有效的权衡了数据可靠性和性能之间的关系。

水平扩展

Kafka:分区
消息被顺序追加到每个分区日志文件的尾部,kafka中的分区可以分布在不同的服务器(broker)上,也就是说,一个主题可以横跨多个broker,以此来提供比单个broker更强大的性能。

每一条消息被发送到broker之前,会根据分区规则选择存储到哪个分区。如果分区规则合理,所有的消息都可以均匀地分配到不同地分区中。如果一个主题只对应一个文件,那么这个文件所在地机器IO将会成为这个主题地性能瓶颈,而分区解决了这个问题。创建分区地时候可以通过指定地参数来设置分区地个数,当然也可以在主题创建完成后对其进行修改,通过增加分区来实现水平扩展。

RabbitMQ的队列是基本存储单元,不再被分区或者分片,对于我们创建了的队列,消费端要指定从哪一个队列接收消息。在存储和IO方面貌似都不具备kafka这样天生的水平扩展能力。

负载均衡

Kafka控制负载均衡的策略措施:
1.一个broker通常就是一台服务器节点。对于同一个Topic的不同分区,Kafka会尽力将这些分区分布到不同的Broker服务器上,zookeeper保存了broker、主题和分区的元数据信息。分区leader会处理来自客户端的生产请求,kafka分区leader会被分配到不同的broker服务器上,让不同的broker服务器共同分担任务。
2.kafka的消费者组订阅同一个topic,会尽可能地使得每一个消费者分配到相同数量的分区,分摊负载。
3.当消费者加入或者退出消费者组的时候,还会触发再均衡,为每一个消费者重新分配分区,分摊负载。
注释:为了能够有效地治理负载失衡的情况,kafka引入了优先副本(preferred replica)的概念。理想情况下,优先副本就是该分区的leader副本。Kafka要确保所有主题的优先副本在kafka集群中均匀分布,这样就保证了所有分区的leader均衡分布。如果leader分布过于集中,就会造成集群负载不均衡。需要注意的是,分区平衡并不意味着kafka集群的负载均衡,因为还要考虑集群中的分区分配是否均衡。更进一步,每个分区的leader副本的负载也是各不相同的,有些要承担的负载比较高,有的比较低。

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技术,或者在客户端使用算法实现负载均衡,引入负载均衡之后,各个客户端的连接可以分摊到集群的各个节点之中。

消息持久化

Kafka中消息存储于日志文件中,如果不加配置的话,在默认的配置参数的作用下,消息会被定期删除。
RabbitMQ的持久化分为三个部分:交换器的持久化、队列的持久化和消息的持久化。可以将所有消息都设置为持久化,但是这样会严重影响RabbitMQ的性能,写入磁盘的速度比写入内存的速度慢很多。

注意:RabbitMQ并不会为每条消息都进行同步存盘(调用内核的fsync方法)的处理,可能仅仅保存到操作系统的缓存之中而不是物理磁盘之中,因此也有可能出现持久化失败的情况。解决这个问题的方法有镜像机制和事务机制等。

避免消息重复

由于kafka和RabbitMQ都可以对发送方和接收方的消息确认机制进行自定义设计,采用合适的策略都可以实现“at least once”和”at most once”。而RocketMQ仅能支持“at least once”,这使得其性能得到了不小的提升。其实对于消息中间件来说,确实只要向用户保证消息可以不丢失就行了,因为机制漏洞导致的消息重复完全可以在消费端解决,只要去重就可以了。

消息确认

Kafka和RabbitMQ都有发送方确认机制和接收确认机制,而且两者都可以采用默认的自动确认机制,两者区别不大。

消息回溯

由于Kafak时刻记录了offset信息,并在消息实体中添加了时间属性,kafka有提供了seek()方法实现从特定点(时间点和物理存储点)进行消费。因此可以比较容易地实现消息回溯。
而RabbitMQ不具备这个特性。

消息过期

Kafka中使用自定义的消费者拦截器使用消息的timeStamp字段来判定是否过期。
RabbitMQ目前有两种方法可以设置消息的TTL。第一种方法是通过队列属性设置,队列中所有消息都有相同的过期时间,第二种方法是对消息本身进行单独设置,每条消息的TTL可以不同。消息在队列中的生存时间一旦超过设置的TTL值时,就会变成“死信”,消费者将无法再收到该消息(并不绝对)。

正则匹配主题

在kafka中我们可以利用subscribe地重载方法以正则表达式为参数订阅特定模式的主题。
在RabbitMQ中利用topic exchange可以以正则表达式的形式订阅特定模式的主题。

生产者与消息生产

Kafka:

  1. KafkaProducer是线程安全的,可以在多个线程中共享单个KafkaProducer实例,也可以将KafkaProducer实例进行池化来供其他线程使用。
  2. Kafka中生产者发送消息主要有三种模式:发后即忘、同步和异步
  3. Kafka中,在生产者发送完消息之后,可以获得一个RecordMetadata对象(send()方法是有返回值的)。在RecordMetadata对象里包含了消息的一些元数据信息,比如当前消息的主题、分区、分区中的偏移量和时间戳等。可以利用这些信息进行生产操作。
    RabbitMQ:
    1.默认情况下,publisher到server的过程中,server不会将publisher的执行情况返回给producer。生产者只知道发送了,不知道是否得到存储,更不知道是否被消费。
    2.有一个模式两个标志位来实现与Kafka类似的功能:confirm模式、mandatory标志位和immediate标志位。

消费者与消息消费

与其他一些消息中间件不同的是:在Kafka的消费理念中还有一层消费者组(consumer group)的概念。每个消费者都有一个对应的消费组。当消费发布到主题后,只会被投递给订阅它的每个消费组中的一个消费者。
消费者与消费组这种模型可以让整体的消费能力具备横向伸缩性,我们可以增加或减少消费者的个数来提高或降低整体的消费能力。对于分区固定的情况。
对于Kafak而言,增加一个消费组的概念无非是多记录一组组内consumerId、offset等信息。
而在RabbitMQ中,实现Publish/Subscribe模式的方法是为每个消费者新增一个队列,相较Kafka的设计思想耦合度略高。

吞吐量及吞吐量性能测试

性能测试:kafka可以在服务端用线程的脚本测量吞吐量;RabbitMQ可以借助现有插件来观测吞吐性能。
吞吐比较:Kafka:极大 Kafka按批次发送消息和消费消息。发送端将多个小消息合并,批量发向Broker,消费端每次取出一个批次的消息批量处理; rabbitmq:比较大。

你可能感兴趣的:(JMS)