消息队列-功能、性能、运维对比

一、功能

1.1 消费推拉模式

图片

1.2 延迟队列


消息延迟投递,当消息产生送达消息队列时,有些业务场景并不希望消费者立刻收到消息,而是等待特定时间后,消费者才能拿到这个消息进行消费。

延迟队列一般分为两种,基于消息的延迟和基于队列的延迟:

基于消息的延迟:为每条消息设置不同的延迟时间,当队列有新消息进入的时候根据延迟时间排序,当然这样会对性能造成较大影响。
基于队列的延迟:设置不同延迟级别的队列,队列中每个消息的延迟时间都是相同的,这样免去了基于延迟时间排序对性能带来的损耗,通过一定的扫描策略即可投递超时的消息。
延迟消息的使用场景比如异常检测重试,订单超时取消等,例如:

服务请求异常,需要将异常请求放到单独的队列,隔5分钟后进行重试;

用户购买商品,但一直处于未支付状态,需要定期提醒用户支付,超时则关闭订单;

面试或者会议预约,在面试或者会议开始前半小时,发送通知再次提醒。

不同MQ支持情况如下:

Kafka:不支持延迟消息

Pulsar:支持秒级的延迟消息,所有延迟投递的消息会被Delayed Message Tracker记录对应的index,consumer在消费时,会先去Delayed Message Tracker检查,是否有到期需要投递的消息,如果有到期的消息,则从Tracker中拿出对应的index,找到对应的消息进行消费,如果没有到期的消息,则直接消费正常的消息。对于长时间的延迟消息,会被存储在磁盘中,当快到延迟间隔时才被加载到内存里。

RocketMQ:开源版本延迟消息临时存储在一个内部主题中,不支持任意时间精度,支持特定的level,例如定时5s,10s,1m等。

RabbitMQ:需要安装一个rabbitmq_delayed_message_exchange插件。

1.3 死信队列


由于某些原因消息无法被正确的投递,为了确保消息不会被无故的丢弃,一般将其置于一个特殊角色的队列,这个队列一般称之为死信队列。与此对应的还有一个“回退队列”的概念,试想如果消费者在消费时发生了异常,那么就不会对这一次消费进行确认(Ack), 进而发生回滚消息的操作之后消息始终会放在队列的顶部,然后不断被处理和回滚,导致队列陷入死循环。

为了解决这个问题,可以为每个队列设置一个回退队列,它和死信队列都是为异常的处理提供的一种机制保障。实际情况下,回退队列的角色可以由死信队列和重试队列来扮演。

不同MQ支持情况如下:

Kafka:没有死信队列,通过Offset的方式记录当前消费的偏移量。

Pulsar:有重试机制,当某些消息第一次被消费者消费后,没有得到正常的回应,则会进入重试Topic中,当重试达到一定次数后,停止重试,投递到死信Topic中。

RocketMQ:通过DLQ来记录所有消费失败的消息。

RabbitMQ:利用类似于延迟队列的形式实现死信队列。

1.4 优先级队列


优先级队列不同于先进先出队列,优先级高的消息具备优先被消费的特权,这样可以为下游提供不同消息级别的保证。

不过这个优先级也是需要有一个前提的:如果消费者的消费速度大于生产者的速度,并且消息中间件服务器(一般简单的称之为Broker)中没有消息堆积,那么对于发送的消息设置优先级也就没有什么实质性的意义了,因为生产者刚发送完一条消息就被消费者消费了,那么就相当于Broker中至多只有一条消息,对于单条消息来说优先级是没有什么意义的。

不同MQ支持情况如下:

Kafka、RocketMQ、Pulsar不支持优先级队列,可以通过不同的队列来实现消息优先级。

RabbitMQ:支持优先级消息。

1.5 消息回溯


一般消息在消费完成之后就被处理了,之后再也不能消费到该条消息。消息回溯正好相反,是指消息在消费完成之后,还能消费到之前被消费掉的消息。

对于消息而言,经常面临的问题是“消息丢失”,至于是真正由于消息中间件的缺陷丢失还是由于使用方的误用而丢失一般很难追查,如果消息中间件本身具备消息回溯功能的话,可以通过回溯消费复现“丢失的”消息进而查出问题的源头之所在。

消息回溯的作用远不止与此,比如还有索引恢复、本地缓存重建,有些业务补偿方案也可以采用回溯的方式来实现。

不同MQ支持情况如下:

Kafka:支持消息回溯,可以根据时间戳或指定Offset,重置Consumer的Offset使其可以重复消费。

Pulsar:支持按时间对消息进行回溯。

RocketMQ:支持按时间回溯,实现的原理跟Kafka一致。

RabbitMQ:不支持回溯,消息一旦标记确认就会被标记删除。

1.6 消息持久化


流量削峰是消息中间件的一个非常重要的功能,而这个功能其实得益于其消息堆积能力。从某种意义上来讲,如果一个消息中间件不具备消息堆积的能力,那么就不能把它看做是一个合格的消息中间件。

消息堆积分内存式堆积和磁盘式堆积。一般来说,磁盘的容量会比内存的容量要大得多,对于磁盘式的堆积其堆积能力就是整个磁盘的大小。从另外一个角度讲,消息堆积也为消息中间件提供了冗余存储的功能。

不同MQ支持情况如下:

Kafka和RocketMQ:直接将消息刷入磁盘文件中进行持久化,所有的消息都存储在磁盘中。只要磁盘容量够,可以做到无限消息堆积。

RabbitMQ :是典型的内存式堆积,但这并非绝对,在某些条件触发后会有换页动作来将内存中的消息换页到磁盘(换页动作会影响吞吐),或者直接使用惰性队列来将消息直接持久化至磁盘中。

Pulsar:消息是存储在BookKeeper存储集群上,也是磁盘文件。

1.7 消息确认机制


消息队列需要管理消费进度,确认消费者是否成功处理消息,使用push的方式的消息队列组件往往是对单条消息进行确认,对于未确认的消息,进行延迟重新投递或者进入死信队列。

不同MQ支持情况如下:

Kafka:通过Offset的方式确认消息。

RocketMQ:与Kafka类似也会提交Offset,区别在于消费者对于消费失败的消息,可以标记为消息消费失败,Broker会重试投递,如果累计多次消费失败,会投递到死信队列。

RabbitMQ:消费者确认单条消息,否则会重新放回队列中等待下次投递。

Pulsar:使用专门的Cursor管理。累积确认和Kafka效果一样;提供单条或选择性确认。

1.8 消息TTL


消息TTL表示一条消息的生存时间,如果消息发出来后,在TTL的时间内没有消费者进行消费,消息队列会将消息删除或者放入死信队列中。

不同MQ支持情况如下:

Kafka:根据设置的保留期来删除消息。有可能消息没被消费,过期后被删除。不支持TTL。

Pulsar:支持TTL,如果消息未在配置的TTL时间段内被任何消费者使用,则消息将自动标记为已确认。消息保留期与消息TTL之间的区别在于:消息保留期作用于标记为已确认并设置为已删除的消息,而TTL作用于未ack的消息。上面的图例中说明了Pulsar中的TTL。例如,如果订阅B没有活动消费者,则在配置的TTL时间段过后,消息M10将自动标记为已确认,即使没有消费者实际读取该消息。

RocketMQ:提及到消息TTL的资料比较少,不过看接口似乎是支持的。

RabbitMQ:有两种方式,一个是声明队列的时候在队列属性中设置,整个队列中的消息都有相同的有效期。还可以发送消息的时候给消息设置属性,可以位每条消息都设置不同的TTL。

1.9 多租户隔离


多租户是指通过一个软件实例为多个租户提供服务的能力。租户是指对系统有着相同“视图”的一组用户。不支持多租户的系统里边,往往要为不同用户或者不同集群创建多个消息队列实例实现物理隔离,这样会带来较高的运维成本。

作为一种企业级的消息系统,Pulsar的多租户能力按照设计可满足下列需求:

确保严苛的SLA可顺利满足。

保证不同租户之间的隔离。

针对资源利用率强制实施配额。

提供每租户和系统级的安全性。

确保低成本运维以及尽可能简单的管理。

Pulsar通过下列方式满足了上述需求:

通过为每个租户进行身份验证、授权和ACL(访问控制列表)获得所需安全性。

为每个租户强制实施存储配额。

以策略的方式定义所有隔离机制,策略可在运行过程中更改,借此降低运维成本并简化管理工作。

1.10 消息顺序性


消息顺序性是指保证消息有序。消息消费顺序跟生产的顺序保持一致。

不同MQ支持情况如下:

Kafka:保证了分区内的消息有序。

Pulsar:支持两种消费模式,独占订阅的流模式只保证了消息的顺序性,共享订阅队列模型不保证有序性。

RocketMQ:需要用到锁来保证一个队列同时只有一个消费者线程进行消费,保证消息的有序性。

RabbitMQ:RabbitMQ的顺序性的条件比较苛刻,需要单线程发送、单线程消费,并且不采用延迟队列、优先级队列等高级功能。
 

1.11 消息查询

在实际开发中,经常要查看MQ中消息的内容,比如通过某个MessageKey/ID,查询到MQ的具体消息。或者是对消息进行链路追踪,知道消息从哪里来,发送到哪里去,进而快速对问题进行排查定位。

不同MQ支持情况如下:

Kafka:存储层是以分布式提交日志的形式实现,每次写操作都顺序追加到日志的末尾。读也是顺序读。不支持检索功能。

Pulsar:可以通过消息ID,查询到具体某条消息的消息内容、消息参数和消息轨迹。

RocketMQ:支持按Message Key、Unique Key、Message Id对消息进行查询。

RabbitMQ:使用基于索引的存储系统。这些将数据保存在树结构中,以提供确认单个消息所需的快速访问。由于RabbitMQ的消息在确认后会被删除,因此只能查询未确认的消息。

1.12 消费模式

不同MQ支持情况如下:

Kafka:有两种消费模式,最终都会保证一个分区只有1个消费者在消费:

  • subscribe方式:当主题分区数量变化或者consumer数量变化时,会进行rebalance;注册rebalance监听器,可以手动管理offset不注册监听器,kafka自动管理。

  • assign方式:手动将consumer与partition进行对应,kafka不会进行rebanlance。

Pulsar:有四种消费模式,其中独占模式和灾备模式跟Kafka类似,为流模型,每个分区只有1个消费者消费,能保证消息有序性。共享模式和Key共享模式为队列模型,多个消费者能提高消费速度,但不能保证有序性。

  • Exclusive独占模式(默认模式):一个Subscription只能与一个Consumer关联,只有这个Consumer可以接收到Topic的全部消息,如果该Consumer出现故障了就会停止消费。

  • 灾备模式(Failover):当存在多个consumer时,将会按字典顺序排序,第一个consumer被初始化为唯一接受消息的消费者。当第一个consumer断开时,所有的消息(未被确认和后续进入的)将会被分发给队列中的下一个consumer。

  • 共享模式(Shared):消息通过round robin轮询机制(也可以自定义)分发给不同的消费者,并且每个消息仅会被分发给一个消费者。当消费者断开连接,所有被发送给他,但没有被确认的消息将被重新安排,分发给其它存活的消费者。

  • KEY共享模式(Key_Shared):当存在多个consumer时,将根据消息的 key进行分发,key相同的消息只会被分发到同一个消费者。

RocketMQ:有两种消费模式,BROADCASTING(广播模式),CLUSTERING(集群模式)

广播消费指的是:一条消息被多个consumer消费,即使这些consumer属于同一个ConsumerGroup,消息也会被ConsumerGroup中的每个Consumer都消费一次,广播消费中ConsumerGroup概念可以认为在消息划分方面无意义。

集群消费模式:一个ConsumerGroup中的Consumer实例平均分摊消费消息。例如某个Topic有9条消息,其中一个ConsumerGroup有3个实例(可能是3个进程,或者3台机器),那么每个实例只消费其中部分,消费完的消息不能被其他实例消费。

RabbitMQ:都是跟Pulsar共享模式类似的,队列的形式,增加一个消费者组里的消费者数量能提高消费速度。

1.13 消息可靠性

消息丢失是使用消息中间件时所不得不面对的一个同点,其背后消息可靠性也是衡量消息中间件好坏的一个关键因素。尤其是在金融支付领域,消息可靠性尤为重要。

比如当服务出现故障时,一些对于生产者来说已经生产成功的消息,是否会在高可用切换时丢失。同步刷盘是增强一个组件可靠性的有效方式,消息中间件也不例外,Kafka和RabbitMQ都可以支持同步刷盘,但绝大多数情景下,一个组件的可靠性不应该由同步刷盘这种极其损耗性能的操作来保障,而是采用多副本的机制来保证。

不同MQ支持情况如下:

Kafka:可以通过配置request.required.acks参数设置可靠级别,表示一条消息有多少个副本确认接收成功后,才被任务发送成功。

  • request.required.acks=-1 (全量同步确认,强可靠性保证)

  • request.required.acks=1(leader确认收到,默认)

  • request.required.acks=0 (不确认,但是吞吐量大)

Pulsar:有跟Kafka类似的概念,叫Ack Quorum Size(Qa),Qa是每次写请求发送完毕后需要回复确认的Bookie的个数,其数值越大则需要确认写成功的时间越长,其值上限是副本数Qw。为了一致性,Qa应该是:(Qw+1)/2或者更,即为了确保数据安全性,Qa下限是 (Qw+1)/2。

RocketMQ:与Kafka类似。

RabbitMQ:是主从架构,通过镜像环形队列实现多副本及强一致性语义的。多副本可以保证在master节点宕机异常之后可以提升slave作为新的master而继续提供服务来保障可用性。

二、性能

在性能测试上,有很多客户端、服务端参数设置、机器性能配置等影响,比如消息可靠性级别,压缩算法等,很难做到“完全”控制变量公平的测试。但是有几个关注点:

  • RabbitMQ的延迟是微秒级的,其他组件的延迟都是毫秒级,RabbitMQ应该是MQ组件里相对来说较低的。

  • Kafka单实例在主题/分区数比较多的情况下,性能会明显降低:

  1. kafka是一个分区一个文件,当topic过多,分区的总量也会增加,kafka中存在过多的文件,当对消息刷盘时,就会出现文件竞争磁盘,出现性能的下降。

  2. 还有Kafka每个消费者加入或退出都会进行重平衡,当分区数比较多时重平衡可能耗时较久,在重平衡的阶段消费者是不能消费消息的。

  • 而Pulsar由于存储与计算分离的架构,使得它可以支持百万级别的Topic数量。

Pulsar和Kafka都被广泛用于各个企业,也各有优势,都能通过数量基本相同的硬件处理大流量。部分用户误以为Pulsar使用了很多组件,因此需要很多服务器来实现与Kafka相匹敌的性能。

这种想法适用于一些特定硬件配置,但在多数资源配置相同的情况中,Pulsar的优势更加明显,可以用相同的资源实现更好的性能。

举例来说,Splunk最近分享了他们选择Pulsar放弃Kafka的原因,其中提到“由于分层架构,Pulsar帮助他们将成本降低了30%-50%,延迟降低了80%-98%,运营成本降低了33%-50%”。Splunk 团队发现Pulsar可以更好地利用磁盘IO,降低CPU利用率,同时更好地控制内存。

在分布式系统里,单机性能指标虽然也很重要,分布式系统整体的性能以及灵活扩缩容、高可用容灾等能力也会是评估的一个重要参考。MQ中间件具体的性能指标,也需要我们自己根据实际的情况,根据实际购买的集群配置和客户端参数,进行压测调优来评估。

三、运维

在使用过程中难免会出现各种异常情况,比如宕机、网络抖动、扩容等。消息队列具备异地容灾,高可用架构等能力,能避免一些计算节点、网络等基础设施不可用导致的故障。

3.1 高可用

不同MQ支持情况如下:

Kafka:通过分区多副本的方式解决高可用问题。

Pulsar:Pulsar计算集群Broker是无状态的,可以灵活扩缩容,存储节点Bookie上通过消息分区分片副本的方式,每个分片都有一个或多个副本,保证在某一个Bookie挂掉后,有其他分片可以提供服务。

RocketMQ和RabbitMQ:都是主从架构,当master挂掉后,由原来的从节点继续提供服务。备机提供消费服务,保证消息不丢,但不提供写服务。

3.2 跨地域容灾

Pulsar原生支持跨地域容灾功能,在这个图中,每当P1、P2和P3的生产者分别向Cluster-A、Cluster-B和Cluster-C中的T1 topic发送消息时,这些消息很快在不同的集群中复制。一旦消息完成复制,消费者C1和C2会从各自的集群消费到这个消息。

在这个跨地域容灾的设计支撑下,其一,我们可以比较容易的将服务分散到多个机房;其二,可以应对机房级别的故障,即在一个机房不可用的情况下,服务可以转接到其它的机房来继续对外提供服务。

一句话概括,Pulsar的跨地域复制,其实就是在一个本地集群中创建一个 Producer,把异地的集群作为这个Producer的发送地址,将本地集群的消息发送过去,并且在本地维护一个Cusor来保证消息可靠性和幂等性。

3.3 集群扩容 

当消息量突然上涨,消息队列集群到达瓶颈的时候,需要对集群进行扩容,扩容一般分为水平扩容和垂直扩容两种方式,水平扩容指的是往往集群中增加节点,垂直扩容指的是把集群中部分节点的配置调高,增加处理能力。

不同MQ支持情况如下:

Kafka:Kafka集群由于主题分区是物理存储在Broker节点上的,新加入的集群的节点并没有存储分区分片,也就无法提供马上提供服务,因此需要把一些Topic的分区分配到新加入的节点里,这里会涉及到一个分区数据均衡的过程,将某些分区的数据复制到新节点上。这个过程跟分区当前堆积的数据量、Broker性能有关,有可能会出现由于源Broker负载过高,堆积数据过大,导致数据均衡的时间变长。

Pulsar:以无限分布式日志以分片为中心,借助扩展日志存储(通过Apache BookKeeper)实现,内置分层存储支持,因此分片可以均匀地分布在存储节点上。由于与任一给定topic相关的数据都不会与特定存储节点进行捆绑,因此很容易替换存储节点或缩扩容。另外,集群中最小或最慢的节点也不会成为存储或带宽的短板。

RocketMQ:新节点直接加入到集群中,在新的broker创建新topic并且分配队列,或者在已有topic基础上分配队列。与Kafka的区别是,Kafka的分区是在不同的物理机器上,而Rocketmq是逻辑分区,用的队列形式,因此不存在出现数据不均衡的情况。

RabbitMQ:由于不涉及过多的消息持久化,直接往集群中增加节点。

四、总结

分类功能项KafkaPulsarRocketMQRabbitMQ
功能消费推拉模式pullpushpullpush
延迟队列
死信队列
优先级队列
消息回溯
消息持久化
消息确认机制OffsetOffset+单条Offset单条
消息TTL
多租户隔离
消息顺序性分区有序流模式有序消费者加锁
消息查询
消费模式流模式流模式+队列模式广播模式+集群模式队列模式
消息可靠性request.required.acksAck Quorum Size(Qa)与Kafka类似镜像模式
性能单机吞吐量605MB/S605MB/S类Kafka38MB/S
消息延迟5ms5msms级微秒级
支持主题数几十到几百上百万个几百到几千上千个
运维高可用分布式架构分布式架构主从架构主从架构
跨地域容灾
集群扩容增加节点,通过复制数据均衡增加节点,通过新增分片均衡增加节点增加节点

Kafka推出的时间较早,各种场景比如日志、大数据处理等都有较成熟的解决方案。

Pulsar作为一个新秀,支持的功能比Kafka更丰富,而且跨地域容灾,多租户等功能,解决了很多Kafka设计缺陷和运维成本问题,整体稳定性更强。很多国内外大公司也有很多Pulsar的实践案例。

因此,一些传统的日志、大数据处理等场景,对高吞吐量有要求的,对消息可靠性的要求没那么高的,可以选用Kafka,有很多优秀的文档说明怎么参数调优提高性能。而一些对消息可靠性、容灾要求更好,或者有高分区、延迟队列等需求的场景,可以选用Pulsar。

参考资料:

10分钟搞懂,消息队列选型全方位对比

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值