一、引言

前面已经分析过不同MQ的实现原理,本文则对他们进行一个横向的对比,以便加深记忆,更加深刻的掌握不同消息队列的特性

1.起源

RabbitMQ

RabbitMQ起源于AMQP(Advanced Message Queuing Protocol,高级消息队列协议)标准

AMQP最初是由金融行业的一些公司共同制定的,旨在解决在金融系统中异步通信的需求

RabbitMQ是基于AMQP标准实现的消息队列中间件,最初由LShift开发,并在2007年成为开源项目

RabbitMQ使用Erlang语言开发,其设计目标是实现高可用、高可靠、高性能的分布式消息队列系统

RocketMQ

淘宝原来有一 个自研的 MQ 叫 Notify,Kafka 开源以后,就参考 Kafka 用 Java 语言写了 MetaQ, 所以在涉及思想上有很多跟 Kafka 相似的地方。后来改名字叫 RocketMQ,2012 年开源

Kafka

Kafka 是由 LinkedIn 公司开发的一种分布式、可扩展、高吞吐量的消息系统

它的设计灵感来自于 LinkedIn 在处理大规模实时数据处理和数据流的挑战中的经验和教训

Kafka 借鉴了消息队列的概念,但与传统的消息系统不同的是,Kafka 的设计重点放在了可扩展性和高吞吐量上

2.整体对比

RabbitMQ Rocket Kafka
单机吞吐量 2.6w/s(消息做持久化) 11.6w/s 17.3w/s
开发语言 Erlang Java Scala/Java
主要维护者 Mozilla/Spring Alibaba Apache
订阅形式 提供了4种,direct,Topic,Headers和fanout(广播模式) 基于Topic/MessageTag,按照消息类型、属性进行正则匹配
发布-订阅模式
基于Topic,按照topic进行正则匹配
发布-订阅模式
持久化 支持少量堆积 支持大量堆积 支持大量堆积
顺序消息 不支持 支持 支持
集群方式 支持简单集群,镜像集群,对高级集群模式支持不好 常用多对Master-Slave模式,开源版本需要手动切换Slave变成Master 天然划分Leader-Slave,无状态集群,每台服务器既能是Master也能是Slave
性能稳定性 一般 较差

二、结构对比

1.整体对比

首先是RabbitMQ,它的Broker是由ExchangeQueue来组成的

image-20230412202855830

其次是RocketMQ,它并没有使用Zookeeper作为注册中心,而是自定义了NameServer这么个结构,可以认为Broker主机由NameServer与Broker Master+Broker Slave组成的,这里所谓的Master和Slave是指Broker的主副本Master和其他副本Slave,它可以有多个Broker结构

image-20230415151544795

最后是Kafka,它采用Zookeeper作为注册中心,负责维护Kafka机器的存活,状态等信息

在Kafka中,每个Topic划分出来了不同的Partition,它的Partition也是分Leader和Follewer的。其中Follower就是它的Leader的备份

image-20230413131142867

2.生产者

2.1 RabbitMQ

RabbitMQ基于AMQP协议,所以RabbitMQ里面也引入了 Channel 的概念,它是一个虚拟的连接,把它翻译成通道,或者消息信道。这样我们就可以在保持的 TCP 长连接里面去创建和释放 Channel,大大了减少了资源消耗

即交换机Exchange和生产者建立长链接,使用Channel发送消息

rabbitMQ也支持事务,就是发送成功返回一个ack,表示发送成功了

2.2 RocketMQ

RocketMQ的生产者会定时从NameServer拉取路由信息(不用配置 RocketMQ 的服务地址),然后根据路由信息与指定的 Broker 建立 TCP 长连接,从而将消息发送到 Broker 中

发送逻辑一致的 Producer 可以组成一个 Group,这个Group里的Pro

RocketMQ 的生产者同样支持批量发送,不过 List 要自己传进去

即通过NameServer定时拉取路由信息,与Broker下的Master建立长链接,支持Group发送

此外,观察下RocketMQ的结构

image-20230415151544795

在RocketMQ中,生产者根据Topic建立连接,但是这里的Topic是一个逻辑概念,可能是由多个Broker(Master-Slave)组成的,所以就产生了一个问题,那就是生产者要和谁建立联系,发给谁呢?

三种策略,轮询,随机或者未实现

此外,RocketMQ还支持事务消息,具体参考RocketMQ事务消息

2.3 Kafka

Kafka为了减少io次数,开辟了一个发送缓冲区,当缓冲区中发送的消息达到一个阈值之后,会将所有缓冲区中的消息一次发送给Broker

Kafka的事务是使用2PC思想来实现的,引入了有事务协调者TC,事务日志

2.4 小结

生产者,三者和Broker建立连接的过程就都不相同

RabbitMQ是和Exchange之间建立连接,Rocket需要用NameServer上同步过来的路由信息和Topic建立长链接,而Kafka则借助Zookeeper获取路由信息,再与Topic建立连接

RabbitMQ借助Channel发送消息,RocketMQ可以分Group发送,Kafka则先缓存再批量发送

此外,他们都支持事务机制,但是实现方式逐渐复杂

最后,rabbitMQ不支持顺序性

3.消费者

3.1 RabbitMQ

支持Push和Pull模式

  • Pull模式:对应的方法是 basicGet,消费者自己获取,实时性低一点,但是能根据自己消费能力获取
  • Push模式:对应的方法是 basicConsume,消息队列主动推送给消费者,实时性高,但是消费不过来可能造成消息积压

3.2 RocketMQ

在RocketMQ中,消费者有两种消费方式,分别是集群消费广播消费

  • 集群消费:多个消费者消费同一个Topic的消息,每个消费一部分
  • 广播消费:每个消费者都会消费同一个Topic下的所有消息,每个都消费全部消息

按照消费模型来说,RocketMQ也有两种模式,分别是PullPush

  • Pull:轮询从Broker拉取消息,使用长轮询实现,所谓长轮询就是如果轮询不到相关数据,就会hold住请求,等到有数据或者等一定时间后再返回,返回后客户端立即发起下一次长轮询
  • push:Broker推送给Consumer,但是在RocketMQ里面,这个模式实际上是依赖Pull实现的,在Pull模式基础上封装了一层,所以它不是真正的“推模式”

消费逻辑一致的 Consumer 可以组成一个 Group,这时候消息会在 Consumer 之间负载

3.3 Kafka

相比上面的Rocket和RabbitMQ都支持push和pull模式,Kafka的实现只有Pull模式

3.4 小结

只有Kafka只支持Pull模式消费,即消费者自己去拉取消费

三者都支持集群和广播消费,但是实现方式各有不同

RocketMQ和Kafka都有消费者组这个概念,即分组消费

4.消息分发

4.1 RabbitMQ

事实上,消息分发主要是RabbitMQ上的,它指定了不同的方式将消息从交换机分发到Queue上

生产者与交换机之间,有一个routing Key,交换机与Queue之间有一个binding key

主要就是依据这两个key之间的关系进行分发

  • direct:直连,两个key完全匹配时才能分发
  • topic:使用正则符号#和*匹配
  • fanout:广播,全都发,Queue都能收到

每个消费者可以订阅多个queue,但是每个queue只能被一个消费者消费

4.2 RocketMQ

在RocketMQ中,生产者与消费者都是依靠Topic进行联系的,但是需要注意,在RocketMQ中,Topic是一个逻辑概念,实际上是与Broker建立的联系,默认只有Master参与读写

Topic 跟生产者和消费者都是多对多的关系,一个生产者可以发送消息到多个Topic,一个消费者也可以订阅多个Topic

4.3 Kafka

首先,在Kafka中,生产和消费也都是与Topic来建立的连接

Kafka采用了分区的思想,一个Topic划分成了不同的Partition,而且每个Partition还采用了分Replica备份的思路,只有作为Leader的Partition具有读写能力,Follower只做灾备

所以很明显,无论生产者,还是消费者,都是和Partition建立的联系

在Kafka中,引入了消费者组的概念,用group id来配置,消费同一个 Topic 的消费者不一定是同一个组,只有 group id 相同的消费者才是同一个消费者组

此外注意:同一个 Group 中的消费者,不能消费相同的Partition——Partition 要在消费者之间分配

4.3 小结

RocketMQ和Kafka都是和Topic建立连接,但是区别是,在RocketMQ中,Topic是一个抽象逻辑概念,在Kafka当中则是真的存在这么一个结构

RocketMQ可以无所顾忌,多对多随便连接,而Kafka要考虑消费者组,同一个组内的消费者不能消费同一个partition两次

5.持久化

5.1 RabbitMQ

内存与磁盘都存储数据,内存到阈值了就换页到磁盘,磁盘到阈值就卡住,不让生产了

5.2 RocketMQ

同一个Topic内的消息都写入到一个文件中

这样缺点是每个消费者都要维护已经消费到的位置

数据存在磁盘上还那么快是因为采用了Page Cache和零拷贝技术

Page Cache就是预读缓冲区

零拷贝就是直接把Page Cache的数据在用户空间中做一个地址映射,就可以直接读取内存了

这里的零拷贝采用的是MMAP

image-20230415201901909

5.3 Kafka

存储了很多不同格式的文件,比如日志文件,索引文件等,整体结构如下

image-20230413160645706

消息慢了要么删(老数据),要么压缩(只留最新key)

那为什么Kafka数据存储在磁盘还那么快呢?

顺序io:追加到文件末尾

索引

批量读写和压缩

零拷贝:直接从DMA到网卡,是sendfile形式零拷贝

image-20230413185453985

三、总结

从上面的对比可以看出,三种消息队列虽然实现功能类似,但是实现方式有很大不同

算是相同功能每个都有自己的实现方案吧

此时再重新看一下这三者的一些区别

1.吞吐量

为什么他们的吞吐量相差那么多呢?

RabbitMQ最慢,尽管它也做了很多优化,但是没用到零拷贝技术,每个连接都是单线程的

而RocketMQ相对快了些,他用多线程,预读取,零拷贝等技术,提升了吞吐量

Kafka更是使用了大量优化策略,文件结构,索引(根据offset找数据),零拷贝,数据压缩等方法,使得它的吞吐量得到了极大的提升

2.订阅方式

这里的区别,主要是由于他们各自的结构决定的

没什么好说的

3.常见问题

3.1 消息堆积怎么处理?

3.2 他们仨有什么区别?

3.3 如何保证消费顺序性?

3.4 如何保证消费幂等性?