Apache Kafka、Apache Pulsar和RabbitMQ都可以用作消息中间件平台,可对比的项目非常多,但是通常最关心的就是性能。在本文中,将专注于系统的吞吐量和延迟,因为这些是生产中事件流系统的主要性能指标。吞吐量测试尤其可以衡量每个系统在利用硬件(特别是磁盘和CPU)方面的效率。延迟测试可衡量每个系统与实时消息传递之间的接近程度,其中包括高达p99.9%的尾部延迟,这是实时和关键业务系统以及微服务架构的核心要求。
从测试结果来看,Kafka提供了最佳的吞吐量,同时提供最低的端到端延迟,最高可达p99.9%。在较低的吞吐量下,RabbitMQ传递消息的延迟非常低。
Kafka
Pulsar
RabbitMQ(镜像)
峰值吞吐量
605MB/s
305MB/s
38MB/s
p99延迟(毫秒)
5毫秒(200MB/s负载)
25毫秒(200MB/s负载)
1ms*(减少至30MB/s的负载)
注意:RabbitMQ延迟在吞吐量高于30MB/s时会显著上升。此外,在更高的吞吐量下,镜像的影响非常明显,而仅使用传统队列而不进行镜像,则可以实现更好的延迟。
本文的结构会首先介绍使用的测试框架,然后介绍测试平台和工作负载。最后将使用各种系统和应用指标对结果进行解释。所有这些都是开源的,因此好奇的开发者可以自己复制结果,也可以更深入地研究所收集的Prometheus指标。与大多数基准测试一样,性能测试是基于一组特定的工作负载/配置的。当然开发者也可以使用自己的工作负载/配置进行比较,以了解如何切换为生产环境。
背景
首先简要介绍每个系统,以了解它们的高级设计和架构,并研究每个系统所做出的取舍。
Kafka是一个开源的分布式事件流平台,是Apache软件基金会五个最活跃的项目之一。Kafka的核心是一个可复制的、分布式的、持久化的提交日志,用于为事件驱动的微服务或大规模流处理应用提供支持。客户端直接向/从代理集群生成或消费事件,代理集群将事件持久化到基础文件系统,并且还自动在集群内同步或异步复制事件,以实现容错和高可用性。
Pulsar是一个开源的分布式发布/订阅消息系统,最初是为队列场景而设计的。最近,它还添加了事件流功能。Pulsar被设计为(几乎)无状态代理实例层,这些实例连接到单独的BookKeeper实例层,该实例实际负责读/写并有选择地持久化存储/复制消息。同类系统中Pulsar并不是唯一的,还有例如Apache DistributedLog和Pravega,它构建于BookKeeper之上,可以提供一些类似Kafka的事件流功能。
BookKeeper是一个开源分布式存储服务,最初设计为Hadoop NameNode的预写日志。它在称为bookies
的服务器实例之间以账本的形式提供消息的持久化存储。每个bookie
出于恢复的目的将每个消息同步写入本地日志,然后异步写入其本地索引存储中。与Kafka代理不同,bookies
不会相互通信,而BookKeeper客户端负责使用仲裁协议在bookies
之间复制消息。
RabbitMQ是一个开源的传统消息中间件,该中间件实现了AMQP消息标准,可满足低延迟队列场景的需要。RabbitMQ由一组代理进程组成,这些代理进程托管用于将消息发布到其中的交换器
以及用于消费消息的队列。可用性和持久性是所提供的各种队列类型的属性。传统队列提供最少的可用性保证。传统镜像队列将消息复制到其他代理,并提高可用性。通过最近引入的仲裁队列可以提供更强的持久性,但是会降低性能。由于本文关注性能,因此将评估限制为传统队列和镜像队列。
分布式系统的持久性
单节点存储系统(例如RDBMS)依赖于对磁盘的全同步写入来确保最大的持久性。但是在分布式系统中,持久性通常来自复制,数据的多个副本相互独立。全同步数据只是在故障确实发生时减少故障影响的一种方式(例如更频繁地进行同步可能会缩短恢复时间)。相反,如果足够多的副本失败,则无论全同步与否,分布式系统都可能无法使用。因此,是否进行全同步只是一个问题,即每个系统选择依赖于什么来进行复制设计。尽管有些紧密依赖于写入磁盘的数据,因此每次写入都需要同步,但其他一些则在设计中处理了这种情况。
Kafka的复制协议经过精心设计,可通过跟踪已同步到磁盘的内容和未同步到磁盘的内容来确保一致性和持久性,而无需全同步写盘。通过少做一些假设,Kafka可以处理范围更广的故障,例如文件系统级损坏或意外的磁盘资源调配,并且不会将不知道的数据的正确性视为理所当然。Kafka还能够利用操作系统来批量写入磁盘,以提高性能。
目前无法明确确定BookKeeper是否在不全同步每个写入的情况下提供相同的一致性保证,特别是在没有同步磁盘持久性的情况下是否可以依靠复制来实现容错。根据检查以及BookKeeper实现了分组的全同步算法这一事实,可以认为它确实依赖于全同步写入来确保其正确性,但是这个结论还需进一步确认。
由于这可能是一个有争议的话题,因此测试过程中在两种情况下都给出了结果,以确保尽可能公平和完整,尽管使用全同步运行Kafka极为罕见,也没有必要。
基准测试框架
对于任何测试,大家都会关注使用了什么框架,以及它是否公平。为此,本次测试选择了OpenMessaging Benchmark Framework(OMB),该框架最初是由Pulsar贡献者开发的。OMB入门容易,它具有基本的工作负载规范,测试结果的指标收集/报告汇总,对3个选定消息系统的支持以及针对每个系统定制的模块化云部署工作流。但需要注意的是,Kafka和RabbitMQ的实现确实存在一些问题,影响了这些测试的公平性和可重复性,因此本次测试做了一些修正,相关的代码可从这里获得。
OMB框架调整
测试软件平台已升级到Java 11、Kafka 2.6、RabbitMQ 3.8.5和Pulsar 2.6(当前的最新版本),通过使用Grafana/Prometheus监控堆栈,跨消息系统、JVM、Linux、磁盘、CPU和网络捕获指标,显著增强了跨3个系统的监控功能。这对于不仅能够报告结果而且能够解释结果至关重要。本次测试增加了对仅生产者测试和仅消费者测试的支持,并支持生成/排出积压,同时还修复了当主题数小于生产者工作节点数时生产者比率计算的一个严重错误。
OMB Kafka驱动调整
本次测试修复了Kafka驱动中的一个严重Bug,该Bug使Kafka生产者无法使用TCP连接,并限制了每个工作实例的单个连接的瓶颈。与其他系统相比,此修复使Kafka的数量合理,即它们现在都使用相同数量的TCP连接与各自的代理进行通信。还修复了Kafka消费者驱动中的一个严重Bug,该Bug中偏移量的提交过于频繁且同步导致性能下降,而其他系统则异步进行。测试还调整了Kafka消费者获取大小和复制线程数,以消除高吞吐量时消息获取的瓶颈,并配置与其他系统同等的代理。
OMB RabbitMQ驱动调整
测试增强了RabbitMQ以使用主键路由和可配置的交换类型(DIRECT
和TOPIC
交换),并且还修复了RabbitMQ集群设置部署工作流中的一个Bug。引入了主键路由来模仿每个主题的分区概念,等同于Kafka和Pulsar上的设置。还为RabbitMQ部署添加了TimeSync工作流程,用于在客户端实例之间同步时间,以进行精确的端到端延迟测量。此外还修复了RabbitMQ驱动中的另一个Bug,以确保准确的端到端延迟测量。
OMB Pulsar驱动调整
对于OMB Pulsar驱动,测试添加了为Pulsar生产者指定最大批处理大小的功能,并关闭了任何全局限制,这些限制可能会人为地在更高的目标速率下限制跨分区生产者队列的吞吐量,测试不需要对Pulsar驱动进行任何其他重大更改。
测试平台
OMB包含基准的测试平台定义(实例类型和JVM配置)和负载驱动配置(生产者/消费者配置以及服务器端配置),这些将被用作测试的基础。所有测试均部署4个工作实例来驱动负载,3个代理/服务器实例,1个监控实例,以及可选的用于Kafka和Pulsar的3实例Apache ZooKeeper集群。在尝试了几种实例类型之后,测试选择了经过网络/存储优化的Amazon EC2实例类型,具有足够的CPU内核和网络带宽来支持磁盘I/O绑定的工作负载。下面会介绍对这些基准配置进行的所有更改,以及进行不同测试的过程。
磁盘
具体来说,测试使用i3en.2xlarge
(8核、64 GB内存、2x2,500GB NVMe固态盘
)来实现25Gbps的高网络传输带宽,以确保测试不受网络限制。这意味着测试将测量相应的最大服务器性能指标,而不仅仅是网络的速度。i3en.2xlarge
实例在2个磁盘之间支持高达655MB/s
的写入吞吐量,这足以使服务器承受压力。根据常规建议以及原始OMB设置,Pulsar使用其中一个磁盘进行日志存储,并使用另一个磁盘进行数据存储,没有对Kafka和RabbitMQ的磁盘设置进行任何更改。
图1:建立两个磁盘上i3en.2xlarge
实例的最大磁盘带宽,使用Linux的dd
命令进行测试,作为吞吐量测试的基准。
磁盘 1
dd if=/dev/zero of=/mnt/data-1/test bs=1M count=65536 oflag=direct
65536+0 records in
65536+0 records out
68719476736 bytes (69 GB) copied, 210.278 s, 327 MB/s
磁盘 2
dd if=/dev/zero of=/mnt/data-2/test bs=1M count=65536 oflag=direct
65536+0 records in
65536+0 records out
68719476736 bytes (69 GB) copied, 209.594 s, 328 MB/s
操作系统调整
此外,对于这3套系统,使用tuned-adm配置文件对操作系统的延迟性能进行了优化,该配置文件禁用了磁盘和网络调度程序的任何动态调优机制,并使用性能调控器进行CPU频率调整。它将p状态标记为每个内核的最高频率,并将I/O调度程序设置为截止时间,以提供可预测的磁盘请求延迟上限。最后还对内核中的电源管理服务质量(QoS)进行了优化,以实现性能优于节能。
内存
与OMB中的默认实例相比,i3en.2xlarge
测试实例拥有几乎一半的物理内存(64GB
对122GB
)。调整Kafka和RabbitMQ与测试实例兼容非常简单,两者都主要依赖于操作系统的页面缓存,随着新实例的增加,页面缓存会自动缩小。
但是,Pulsar代理和BookKeeper的bookies
都依赖堆外/直接内存进行缓存,测试为这两个单独的进程调整了JVM堆/最大直接内存,以便在i3en.2xlarge
实例上正常工作。具体地说,就是将堆大小从每个24GB
(在原始OMB配置中)减半到12GB
,在两个进程和操作系统之间按比例分配可用物理内存。
在测试中遇到了java.lang.OutOfMemoryError: Direct buffer memory
,在高目标吞吐量时直接出现该错误,如果堆大小再小,则会导致bookies
完全崩溃。这是使用堆外内存的系统所面临的典型内存优化问题。虽然直接字节缓冲区可以改善Java GC,但是大幅度优化仍然具有挑战。
吞吐量测试
首先测试的是,在网络、磁盘、CPU和内存资源相同的情况下,每个系统可以达到的峰值稳定吞吐量。峰值稳定吞吐量的定义是最高的平均生产者吞吐量,在此水平下,消费者可以在不增加积压的情况下保持稳定。
全同步的效果
如前所述,Apache Kafka的默认建议配置是使用底层操作系统规定的页面缓存刷新策略将消息刷新/全同步到磁盘(而不是全同步每条消息),并依靠复制来实现持久性。从根本上说,这提供了一种简单有效的方法来摊销Kafka生产者采用的不同批次大小的成本,以在所有条件下实现最大可能的吞吐量。如果将Kafka配置为在每次写入时进行全同步,则通过强制执行全同步系统调用将人为地阻碍性能,而没有任何其他好处。
也就是说,鉴于要讨论这两种情况的结果,仍然有必要了解全同步对Kafka中每次写入的影响。各种生产者批次大小对Kafka吞吐量的影响如下图所示。在达到“最佳点”之前,吞吐量随着批次大小的增加而增加,在“最佳点”,批次大小足够大,底层磁盘会处于满载状态。将每个消息同步到Kafka上的磁盘(图2中的橙色条)会产生更大批量的类似结果。请注意,这些结果仅在本测试平台的SSD上得到了验证。Kafka确实充分利用了所有批处理大小的底层磁盘,或者在较低的批处理大小下最大化IOPS,或者在更高的批处理大小下最大化磁盘吞吐量,即使在强制对每个消息进行全同步时也是如此。
图2:批次大小对Kafka吞吐量(消息/s)的影响,绿色条代表关闭全同步(默认),橙色条代表启用全同步。
就是说,从上表中可以明显看出,使用默认的同步设置(绿色条)可以使Kafka代理更好地管理页面刷新,从而总体上提供更好的吞吐量。特别是,对于较低的生产者批次大小(1KB和10KB),使用默认同步设置的吞吐量要比全同步模式高约3-5倍。但是,对于较大的批次(100KB和1MB),将分摊全同步的成本,吞吐量与默认同步模式大体相当。
Pulsar在生产者上实现了类似的批处理,并跨bookies
对生产的消息进行定额复制。BookKeeper的bookies
在应用级别实现对磁盘的分组提交/同步,以最大化磁盘吞吐量。默认情况下BookKeeper (由bookie
的配置项journalSyncData=true
控制)以全同步模式写入磁盘。
为了覆盖所有场景,还测试了BookKeeper上配置了journalSyncData=false
的情况,以分别与Kafka做对比。但是在BookKeeper的bookies
上遇到了较大的延迟和不稳定性,这表明与刷新有关的排队。测试还使用Pulsar附带的pulsar-perf
工具验证了相同的行为。据了解这是个Bug,因此后续将其从测试中排除。尽管如此,鉴于journalSyncData=true
时可以看到磁盘的吞吐量已达到极限,因此认为这不会影响最终结果。
图3:Pulsar与BookKeeper的journalSyncData=false
性能说明,显示了吞吐量下降和延迟峰值
图4:BookKeeper日志回调队列的增长与journalSyncData=false
RabbitMQ与持久化队列绑定,该持久化队列当且仅当尚未消费消息时才将消息保存到磁盘。与Kafka和Pulsar不同,RabbitMQ不支持再次读取旧消息。从持久性的角度来看,测试表明消费者与生产者保持同步,因此没有注意到对磁盘的任何写入。测试还通过在3个代理的集群中使用镜像队列,使得RabbitMQ提供了与Kafka和Pulsar相同的可用性保证。
测试配置
测试是根据以下原则和预期保证设计的:
- 消息被复制3份,以实现容错功能(具体下面会详述);
- 所有3个系统都启用了批处理以优化吞吐量,最多可批处理1MB的数据,最多10ms;
- Pulsar和Kafka在一个主题上配置了100个分区;
- RabbitMQ不支持主题中的分区,为了匹配Kafka和Pulsar的设置,声明了一个单一直接交换(等同于主题)和链接队列(等同于分区),具体下面会详述。
OMB使用自动速率发现算法,该算法通过以几种速率探测积压来动态得出目标生产者的吞吐量。在许多情况下,会看到确定速率从2.0消息/秒急剧上升到500,000消息/秒。这些严重损害了测试的可重复性和保真度。因此在本次测试中明确地配置了目标吞吐量而没有使用此功能,并在每秒10K、50K、100K、200K、500K和100万个生产者消息中稳定增加了目标吞吐量,其中4个生产者和4个消费者使用1KB的消息。然后观察每个系统针对不同配置提供稳定的端到端性能的最大速率。
吞吐量结果
从结果可以发现Kafka提供了3个系统中的最高吞吐量,后续将在下面的每个系统中更详细地研究这些结果。
图5:所有3个系统的峰值稳定吞吐量的比较:100个主题分区和1KB消息,使用4个生产者和4个消费者
Kafka配置为使用batch.size=1MB
以及linger.ms=10
,供生产者高效批量写入代理。另外,生产者配置了acks=all
以及min.insync.replicas=2
,以确保每个消息向生产者确认之前都已复制到至少2个代理中。测试发现,Kafka能够充分利用每个代理上的2个磁盘,这是存储系统的理想结果。有关详细信息,请参见Kafka的测试驱动配置。
图6:使用默认的推荐全同步设置的Kafka性能。该图显示了Kafka代理上的I/O利用率以及相应的生产者/消费者吞吐量(来源:Prometheus节点指标)。有关详细信息,请参见原始结果。
另外,还使用另一种配置对Kafka进行了基准测试,即使用flush.messages=1
和flush.ms=0
在确认写入之前将所有消息同步到所有副本上的磁盘上。结果显示在下图中,非常接近默认配置:
图7:Prometheus节点指标显示了Kafka代理上的I/O利用率以及相应的生产者/消费者吞吐量。有关详细信息,请参见原始结果。
Pulsar的生产者在排队产生请求方面的方式与Kafka不同。具体来说,它在内部每个分区都有对应的生产者队列,以及这些队列大小的限制,这些限制对来自给定生产者的所有分区的消息数量设置了上限。为了避免Pulsar生产者限制发送的消息数量,测试将每个分区和全局限制都设置为无穷大,同时与基于1MB字节的批处理限制相匹配。
.batchingMaxBytes(1048576) // 1MB
.batchingMaxMessages(Integer.MAX_VALUE)
.maxPendingMessagesAcrossPartitions(Integer.MAX_VALUE);
针对Pulsar,还设置了更高的基于时间的批处理限制:batchingMaxPublishDelayMs=50
,以确保批处理主要基于字节数限制。测试通过不断增加该值,直至对Pulsar最终达到的峰值稳定吞吐量没有可测量的影响,得出了该值。对于复制配置,测试使用ensembleSize=3,writeQuorum=3,ackQuorum=2
,等效于Kafka的配置方式。有关详细信息,请参见Pulsar的测试驱动配置。
通过BookKeeper的设计,即bookies
将数据写入本地的日志和账本中,可以看到,峰值稳定吞吐量实际上是Kafka的一半。这种基本的设计选择会对吞吐量产生深远的负面影响,这直接影响成本。一旦BookKeeper的bookies
上的日志磁盘完全饱和,Pulsar的生产率就将被限制。
图8:Prometheus节点度量指标显示Pulsar的BookKeeper日志磁盘已用完,并在BookKeeper的bookies
中测量了所产生的吞吐量。有关详细信息,请参见原始结果。
为了进一步验证这一点,测试还将BookKeeper配置为在RAID0中使用2个磁盘,这为BookKeeper提供了在2个磁盘上分别写入日志和账本的机会。这时会看到,Pulsar最大化了磁盘的组合吞吐量(~650MB/s
),但仍限于峰值稳定吞吐量的~340MB/s
。
图9:Prometheus节点指标显示RAID0配置使BookKeeper日志磁盘仍然用尽
图10:Prometheus节点指标显示RAID0磁盘已用完,并在Pulsar代理处测量了所产生的吞吐量。有关详细信息,请参见原始结果。
Pulsar具有分层架构,可将BookKeeper的bookies
(存储)与Pulsar代理(用于存储的缓存/代理)分开。为了完整起见,测试还在上面的分层部署中运行了吞吐量测试,该部署将Pulsar代理移动到了另外3个经过计算优化的实例c5n.2xlarge
(带有8核、21GB内存、最高25Gbps网络带宽、基于EBS的存储
),而BookKeeper节点保留在经过存储优化的i3en.2xlarge
实例上。在这种特殊设置下,总共为Pulsar和BookKeeper提供了6个实例/资源,这比给Kafka和RabbitMQ提供了2倍
的CPU资源和33%
的额外内存。
即使在高吞吐量的情况下,系统也大部分受I/O约束,测试没有发现此设置有任何改进。有关此特定场景的完整结果,请参见下表。实际上,Pulsar的2层架构似乎没有任何真正的CPU瓶颈,只会增加更多的开销,即2个JVM占用了更多的内存,网络传输增加了2倍,并且系统架构中有更多的组件。因此可以认为当网络受到限制时(不同于本次测试提供了过多的网络带宽),Pulsar的2层架构将以2倍的速度消耗网络资源,从而导致性能下降。
Pulsar部署模式
峰值生产者吞吐量(MB/s)
305.73
305.69
与Kafka和Pulsar不同,RabbitMQ在主题中没有分区的概念。相反,RabbitMQ使用交换器将消息路由到链接的队列,使用头属性(头交换)、路由键(直接和主题交换)或绑定(扇出交换),消费者可以从中处理消息。为了匹配工作负载的设置,本次测试声明了一个直接交换(等同于主题)和链接队列(等同于分区),每一个都专门用于服务特定的路由键。测试让生产者为所有路由键(轮询)和每个队列对应的消费者生成消息。本次测试还使用了社区建议的最佳实践对RabbitMQ进行了优化:
- 启用复制(队列被复制到集群中的所有节点);
- 禁用消息持久性(队列仅在内存中);
- 启用了消费者自动确认;
- 跨代理的负载均衡队列;
- RabbitMQ每个队列使用专用内核,因此有24个队列(8个vCPU x 3个代理)。
RabbitMQ在复制开销方面表现不佳,这严重降低了系统的吞吐量。测试注意到在此工作负载期间,所有节点都受CPU约束(请参见下图中的y轴绿线),几乎没有余地用于代理任何其他消息。有关详细信息,请参见RabbitMQ测试驱动配置。
图11:RabbitMQ吞吐量 + CPU使用率。有关详细信息,请参见原始结果。
延迟测试
鉴于流处理和事件驱动的架构日益普及,消息系统的另一个关键是消息从生产者到系统再到消费者的管道的端到端延迟,因此本次测试在三个系统上以每个系统可以维持的最高稳定吞吐量,而不过载的前提下对此进行了对比。
为了优化延迟,本次测试更改了所有系统上的生产者配置,以最多仅1毫秒(而不是用于吞吐量测试的10毫秒)批处理消息,并且还使每个系统保持默认建议配置,同时确保可用性。Kafka配置为使用其默认的全同步设置(即关闭全同步),RabbitMQ配置为对队列镜像的同时不保存消息。基于反复运行,本次测试选择以200K消息/秒或200MB/s
的速度对Kafka和Pulsar进行对比,该速度低于此测试平台上300MB/s的单磁盘吞吐量限制,最后可以看到在吞吐量超过30K消息/秒
时,RabbitMQ将面临CPU瓶颈。
延迟测试结果
图12:高可用性配置的标准模式的端到端延迟,在Kafka和Pulsar上测量值为200K消息/秒(1KB消息大小),在RabbitMQ上仅为30K消息/秒,因其无法维持更高的负载。注意:延迟(ms)是越低越好。
Kafka始终保持比Pulsar更低的延迟,RabbitMQ实现了3个系统中最低的延迟,但由于垂直扩展性有限,因此吞吐量低很多。由于测试是有意设置的,因此对于每个系统消费者始终能够与生产者保持同步,几乎所有读取都从这3个系统的缓存/内存中获得。
Kafka的大部分性能可归因于充分优化的消费者读取实现,该实现基于有效的数据组织而没有任何额外的开销。Kafka充分利用Linux页面缓存和零复制机制来避免将数据复制到用户空间中。通常,许多系统(例如数据库)已经建立了应用级缓存,从而为它们提供了更大的灵活性来支持随机的读/写负载。但是,对于消息系统,依靠页面缓存是一个不错的选择,因为典型的负载会执行顺序的读/写操作。Linux内核已经进行了多年优化,以智能地检测这些模式,并采用诸如预读的技术来大大提高读取性能。同样,基于页面缓存构建允许Kafka使用基于sendfile的网络传输,可避免其他数据副本。为了与吞吐量测试保持一致,还测试了Kafka的全同步模式。
Pulsar与Kafka相比采用了不同的缓存方式,其中一些源于BookKeeper中的核心设计,以区分日志和账本存储。除了Linux页面缓存外,Pulsar还采用了多层缓存,即BookKeeper的bookies
上的预读缓存(测试中保留OMB默认值dbStorage_readAheadCacheMaxSizeMb=1024
),托管账本(managedLedgerCacheSizeMB
,本次测试为12GB直接可用内存的12%为2.4GB)。测试中没有发现此多层缓存有任何好处,际上,多次缓存可能会增加部署的总体成本,怀疑12GB的非堆使用情况中会有相当大的填充,以避免前面提到的直接字节缓冲区造成Java GC问题。
RabbitMQ的性能受到了生产者端交换以及消费者端这些交换绑定的队列的影响。测试将吞吐量测试中的镜像设置用于延迟时间测试,尤其是直接交换和镜像队列。由于CPU瓶颈,测试无法将吞吐量提高到高于38K消息/秒,并且以这种速率测量延迟的任何尝试均显示性能显着降低,几乎达到了2秒钟的p99延迟。
逐渐将吞吐量从38K消息/秒降低到30K消息/秒后,就能够获得稳定的吞吐量,此时系统似乎并未被过度利用,证实了1ms的p99延迟明显更好。可以认为在3个节点上复制24个队列的开销似乎会对更高吞吐量下的端到端延迟产生了巨大的负面影响,而吞吐量低于30K消息/秒或30MB/s(洋红色实线)的RabbitMQ可以提供的端到端延迟要比其他两个系统低得多。
总的来说,遵循最佳实践可以使RabbitMQ提供有限的延迟。鉴于延迟测试是故意设置的,以便消费者始终能够跟上生产者,因此RabbitMQ消息传递管道的效率归结为Erlang BEAM VM(以及CPU)处理队列所需要做的上下文切换次数。因此,通过为每个CPU内核分配一个队列来限制它可以提供最低的延迟。此外,使用直接或主题交换可以将复杂的路由(类似于专门用于Kafka和Pulsar上的分区的消费者)路由到特定队列。但是由于没有通配符匹配,直接交换提供了更好的性能,这增加了更多的开销,是本次测试的合适选择。
图13:Kafka、Pulsar和RabbitMQ的端到端延迟,在Kafka和Pulsar上以200K消息/秒(1KB消息大小)测试,在RabbitMQ上以30K消息/秒测试。有关详细信息,请参见原始结果(Kafka,Pulsar和RabbitMQ)。注意:延迟(ms):越低越好。
在本文的开头,已经介绍了Kafka使用建议的默认全同步配置的延迟结果(绿色实线)。在Kafka将每个消息以全同步模式写入磁盘的另一种配置中(绿点虚线),测试发现Kafka的延迟一直低于Pulsar,直到p99.9%为止,而Pulsar(蓝线)在高尾百分比上表现更好。尽管很难在p99.9%或更高的百分比上准确推断出尾巴延迟,但可以认为,对于替代的Kafka全同步配置(绿点虚线),非线性延迟会在p99.9%的百分比上急剧上升。
延迟的权衡
图14:RabbitMQ的端到端延迟:镜像队列(测试中使用的配置)与传统队列(无复制)的速率为10K、20K、30K和40K消息/秒。注意:此图表中y轴的比例为对数。
需要承认,每个系统在设计时都需要权衡。尽管对Kafka和Pulsar不公平,但还是发现在不提供高可用性时与RabbitMQ相比还是比较有趣的,RabbitMQ相对于Kafka和Pulsar而言,两者的交易时延较低,可提供更强的持久性保证,并且可用性比RabbitMQ高3倍。这可能与某些场景(例如,设备位置跟踪)相关,在这些场景中,可以牺牲可用性以获得更好的性能,这是可以接受的,尤其是在场景需要实时消息传递且对可用性问题不敏感的情况下。测试表明,禁用复制后,RabbitMQ可以在更高的吞吐量下保持较低的延迟,尽管改进的吞吐量(100K消息/s)仍然显著低于Kafka和Pulsar所能达到的水平。
即使Kafka和Pulsar速度较慢(在p99分别为~5ms
和~25ms
),它们提供的持久性,更高的吞吐量和更高的可用性对于处理诸如金融交易这样大规模的事件流场景,是非常重要的。对于需要较低延迟的场景,RabbitMQ仅在轻载的情况下才能实现p99~1ms的延迟,因为消息只是在内存中排队而没有复制开销。
在实践中,运维人员需要仔细配置RabbitMQ,保持足够低的速率以维持这些低延迟限制,从而使延迟显著降低。任务很难,甚至实际上不可能在所有场景中以通用的方式实现。总体而言,一个具有较低运维开销和成本的更好的架构选择,可能是针对所有场景选择一个像Kafka这样的持久化系统,该系统可以在所有负载级别上以低延迟提供最佳吞吐量。
总结
在此文中对3个消息系统(Kafka、RabbitMQ和Pulsar)进行了全面、平衡的分析,得出以下结论:
吞吐量:Kafka提供了所有系统中最高的吞吐量,写入速度比RabbitMQ快15倍,比Pulsar快2倍。
延迟:Kafka在较高的吞吐量下提供最低的延迟,同时还提供强大的持久性和高可用性。在所有延迟测试中,Kafka的默认配置均比Pulsar更快,并且在每条消息上设置为全同步时,其速度最高可达p99.9。RabbitMQ可以实现比Kafka更低的端到端延迟,但仅在吞吐量显著降低的情况下。
成本/复杂度:成本往往是绩效的反函数。Kafka作为具有最高稳定吞吐量的系统,由于其高效的设计而提供了所有系统中的最佳价值(即,每字节写入的成本)。此外,从Kafka删除ZooKeeper的工作(请参见KIP-500)正在顺利进行中,并将进一步简化Kafka的架构。
> 本文译自Confluent的官方博客。