消息队列三兄弟♂谁主沉浮

825 阅读7分钟

简介

消息队列主要为了异步场景下实现上下游解耦功能:在传统场景中,上游产生一条消息,比如用户下单了一件商品,系统创建了对应的订单,需要通知下游的物流、支付等系统进行后续处理;消息队列可以使得上游系统(订单)和下游系统(支付/物流等)解耦,上游只管向消息队列中投递消息即可,下游订阅消息并做相关处理,这使得系统具有极大的可扩展和伸缩性 -- 扩展新系统、上下游利用消息队列做缓冲实现“削峰填谷”等。

目前生产环境主流的开源MQ产品有RabbitMQ、Kafka、RocketMQ,今天分析下它们的异同

背景

RabbitMQ使用Erlang语言写的,由Rabbit公司创建,后被VMware公司收购,是老牌MQ的代表。

Kafka使用Scala和Java编写,最初由领英公司开发,后捐献给Apache基金会,目标是为处理实时数据提供一个统一、高吞吐、低延迟的平台

RocketMQ使用Java语言编写,由阿里巴巴公司开发,在Kafka基础上结合阿里内部电商场景自研设计,2012年开源,并于16年捐献给Apache基金会,17年成为Apache基金会顶级项目。在国内软件开发领域,尤其是在线业务和电商物流等场景下,RocketMQ非常流行(有意思的一点是,在国外RocketMQ远没有RabbitMQ热门,相关英文文档都较少)

MQ模型

RabbitMQ实现了AMQP协议,并支持点对点和发布订阅模式,支持同步通信或异步通信;

Kafka仅支持发布订阅模式,适用于实时流式任务处理,支持高并发,是日志传输、大数据分析场景中的常见选型

RocketMQ仅支持发布订阅模式,适用于在线业务场景,可靠性高,并发能力强,还支持一些事务、定时、延迟消息等高级特性。

MQ架构

RabbitMQ中的核心是queue,queue负责存储、分发消息;生产者通过exchange向queue发送消息,exchange承担消息路由的角色,消费者连接到队列并取消息进行处理。这种架构模式使得RabbitMQ具备发布订阅、topic分发、同步RPC调用等通信模型。详情可看 www.rabbitmq.com/getstarted.…

broker在Kafka中是实际的物理服务,多台broker构成Kafka集群。Kafka中的核心是topic,topic是一个逻辑概念,对于实际用户使用/编程角度来讲,只需感知到topic层即可,topic实际是由物理层的partition组成。partition按照leader-follower的架构分布在broker上,消费者实际是跟partition打交道的,这里的partition类似RabbitMQ中的queue。详情可看 www.modb.pro/db/585123

RocketMQ的架构和Kafka非常类似,只不过将partition的概念替换成了message queue,且去掉了leader-follower架构,由实际的broker的master-slave来保证高可用。详情可看 rocketmq.apache.org/zh/docs/dom…

存储架构

RabbitMQ存储结构包含如下部分:

  • index: rabbit_queue_index 负责维护队列中落盘消息的信息,包括消息的存储地点、是否已被交付给消费者、是否已被消费者 ack 等。每个队列都有与之对应的一个 rabbit_queue_index
  • store: rabbit_msg_store 以键值对的形式存储消息,它被所有队列共享,在每个节点中有且只有一个

详情大家可以去这里了解下www.cnblogs.com/wujuntian/p…

Kafka的消息存储以partition为单位;而partition又由多个segment构成,相当于一种分片机制。segment的底层的存储由index和log文件构成,index存储每个消息的offset索引,log文件存储实际的消息内容。详情可见www.cnblogs.com/rickiyang/p…

Kafka的partition由一个leader和多个follower构成高可用系统,所有的读写请求都落到leader上,follower负责同步leader上的消息存储以及在故障等场景下重新选主。

RocketMQ的消息存储主要由四部分构成:

  • commitlog:消息元信息和消息内容的存储文件。单个文件最大为1GiB。以起始消息的偏移字节数命名文件,第一个文件名是00000000000000000000,第二个是00000000001073741824(1073741824 = 102410241024),后续文件依次类推。同一台broker的所有topic消息都统一存储在commitlog文件中。
  • consumequeue:单个broker上的所有topic信息都存储在commitlog文件中,这样消费者查询起来需要重新检索文件,效率低性能差;consumequeue文件按照topic+queueid目录结构组织,单个consumequeue由30w条记录组成,每条记录包含每条消息在commitlog中的偏移量、消息长度和tag hashcode,共计20字节(8+4+8),所以单个consumequeue的大小有约5.4MiB
  • indexfile:IndexFile(索引文件)提供了一种可以通过key或时间区间来查询消息的方法。文件名fileName是以创建时的时间戳命名的,固定的单个IndexFile文件大小约为400M,一个IndexFile可以保存 2000W个索引,IndexFile的底层存储设计为在文件系统中实现HashMap结构,故rocketmq的索引文件其底层实现为hash索引。
  • offset:offset用来管理每个消费队列的不同消费组的消费进度。对offset的管理分为本地模式和远程模式,本地模式是以文本文件的形式存储在客户端,而远程模式是将数据保存到broker端,对应的数据结构分别为LocalFileOffsetStore和RemoteBrokerOffsetStore。默认情况下,当消费模式为广播模式时,offset使用本地模式存储,因为每条消息会被所有的消费者消费,每个消费者管理自己的消费进度,各个消费者之间不存在消费进度的交集;当消费模式为集群消费时,则使用远程模式管理offset,消息会被多个消费者消费,不同的是每个消费者只负责消费其中部分消费队列,添加或删除消费者,都会使负载发生变动,容易造成消费进度冲突,因此需要集中管理。同时,RocketMQ也提供接口供用户自己实现offset管理(实现OffsetStore接口)。远程模式存储位置见config/consumerOffset.json

上述四部分的存储文件在broker上的位置如下所示:

性能表现

RabbitMQ单机一般可以达到几万吞吐量。

单个Kafka集群一个topic的吞吐能力号称可以达到百万TPS。得益于Kafka的分区机制和消息批量发送机制。不同分区间的消息发送和消费是完全独立的,但是过多的分区因单机磁盘IO竞争会造成单机吞吐能力下降,所以一般药控制单个broker的partition数目,一种说法是单机超过64个分区后写入性能严重下降。

单个Rocket集群一个topic的吞吐能力一般可以达到几十万TPS。因为单个broker的所有topic都是写入到同一个commitlog文件,所以topic&队列数量的上升并不会非常影响性能,单broker一般可以支持5w队列。

选型

那在实际的业务场景中,该如何去选择合适的MQ类型呢?其实没有非常绝对的优劣,通常大部分业务场景下任意选一种影响都不是很大,但是通常会有以下的偏爱:

  • 国内的JAVA用户更加倾向于RocketMQ和Kafka,因为这两都是由java写的。
  • 一般Kafka更适合大数据、日志传输等离线场景,因为Kafka的吞吐性能要优于其他两者
  • RocketMQ更适合在线业务场景,因为它具有较高稳定性,另外还有很多的高级特效:如消息重试、延迟、定时消息,这是从阿里实际场景中孵化出来的产品,相关业务场景可以优先选择
  • RabbitMQ是个老牌的MQ产品,这两年好像渐渐被前两者所掩盖。但是它是非常标准的AMQP协议实现,有非常丰富的消息中间件的功能,可以作为学习研究对象。

总而言之,每个MQ产品都是有可取之处,如何利用好它并解决实际的业务场景,这才是最关键的!