跳至主内容

仲裁队列和流量控制 - 单个队列基准测试

·阅读 13 分钟
Jack Vanlightly

在上一篇文章中,我们介绍了流量控制的概念,以及 RabbitMQ 中可用的各种流量控制机制。我们看到,发布者确认和消费者确认不仅仅是数据安全措施,它们在流量控制中也起着作用。 

在本文中,我们将探讨应用程序开发人员如何在单个队列的上下文中,利用发布者确认和消费者确认来平衡安全性和高性能。 

当代理过载时,流量控制尤其重要。单个队列不太可能使您的代理过载。如果您发送大消息,那确实可以使您的网络饱和,或者如果您只有一个 CPU 核心,那么一个队列可能会使其达到最大利用率。但我们大多数人使用的都是 8、16 或 30+ 核心的机器。但是,分析确认和确认对单个队列的影响是很有趣的。从那里,我们可以吸取经验教训,看看它们是否适用于更大的部署(下一篇文章)。

流水线和发布者确认

发布者可以根据飞行中的未确认消息数量来限制自己的速率。如果设置为 5,发布者将发送 5 条消息,然后阻塞直到收到确认。如果收到一条确认,发布者就可以再发送一条消息并再次阻塞。如果有三条确认消息到来,发布者就可以再发送三条,依此类推。

确认消息可以通过使用multiple 标志进行批量处理。这允许代理一次确认多条消息。如果有 100 条消息正在等待确认,序列号为 1-100,代理可以发送一个带有 multiple 标志且序列号为 100 的确认消息。这减少了发布者和代理之间的通信量,从而提高了效率。

这种流水线方法将产生最高且最稳定的吞吐量。您可以在教程 7 的策略 #3 中找到如何实现此目标的示例代码。有Java 版本和C# 版本。同样的方法也可以应用于其他语言。

流水线和消费者确认

RabbitMQ 采用流水线方法,其中“飞行中限制”是在给定通道上设置的消费者预取(QoS)。如果没有预取,它将尽可能快地发送消息,直到没有更多消息可发送,或者由于客户端 TCP 缓冲区已满而应用 TCP 反压。这可能会使消费者过载,因此请使用预取!

当您的消费者发送确认时,这就像代理向发布者发送确认一样。它允许将更多消息推送到通道。

就像确认一样,消费者确认也可以使用 multiple 标志。消费者可以选择单独确认每条消息,或者每 N 条消息确认一次。我们将此称为确认间隔。当确认间隔设置为 10 时,消费者将使用 multiple 标志每 10 条消息确认一次。

这可能会导致更复杂的代码,因为您还需要考虑

  • 如果最后 10 条消息包含确认、否定确认和拒绝的混合,那么您不能简单地使用带有 multiple 标志的单个确认。
  • 您可能希望为两次确认之间的时间设置一个时间限制,以防消息到达速度很慢,例如,每 10 条消息或最多 50 毫秒。

测量飞行中限制、预取和确认间隔的影响

了解其影响的最佳方法是运行一系列基准测试,在典型的集群上更改发布者确认的飞行中限制、消费者预取和确认间隔。

所有基准测试均在 AWS 上使用以下配置运行

  • c5.4xlarge EC2 实例:16 vCPU(Cascade Lake/Skylake Xeon),32 GB RAM,5 Gbps 网络,200 GB SSD(io1,10000 IOPS)
  • 集群中有 3 个代理
  • 1 个具有相同规格(c5.4xlarge)的负载生成 EC2 机器
  • 1kb 消息
  • 无处理时间,因为这是一个纯粹的吞吐量/延迟基准测试,涉及单个发布者和单个消费者。

我们测试了仲裁队列和镜像队列,以了解仲裁队列与其旧对应队列的区别。

镜像队列有一个主节点和一个镜像节点,而仲裁队列使用三倍的复制因子(一个领导者 + 两个跟随者)。这并不是完全公平的竞争,镜像队列的复制因子为 2,仲裁队列的复制因子为 3,但这分别是它们最常见的配置。所有测试都使用了 RabbitMQ 3.8.4 的 alpha 版本,其中包含用于处理高负载的新仲裁队列功能。

基准测试

  1. 增加飞行中限制、预取 1000、确认间隔 1
  2. 1000 飞行中限制、增加预取、确认间隔 1
  3. 1000 飞行中限制、1000 预取、增加确认间隔
  4. 无确认、无应答

解读这些结果

规则

  • 规则 1 - 这些是合成基准测试,使用了特定版本的 RabbitMQ,云实例(这会引入各种可复现性问题),以及特定的硬件配置。没有单一的基准测试结果,只有无限多的结果。所以不要只看具体的数字,要看趋势和模式。
  • 规则 2 - 这些结果使用的是 Java 客户端,而不是 Spring、Python 或其他语言或框架。但是,我们测试的内容应该适用于其他框架,因为它们必须使用相同的设置,而它们使用这些设置的方式可能在您的控制之下,也可能不在。
  • 规则 3 - 尝试使用您现有的代码并更改这些不同的设置,自己看看效果!

基准测试 #1 - 增加飞行中限制、预取 1000、确认间隔 1

这是一项持续 30 分钟的基准测试,我们每 5 分钟增加一次飞行中限制,值为:1、5、20、200、1000、10000。在低值时,发布者将非常严格地限制自己的速率,从而限制吞吐量,但随着限制的增加,我们应该会看到吞吐量增加。

镜像队列

Fig 1. Mirrored queue with increasing publisher confirm in-flight limit
图 1. 镜像队列,发布者确认飞行中限制增加

仲裁队列

Fig 2. Quorum queue with increasing publisher confirm in-flight limit
图 2. 仲裁队列,发布者确认飞行中限制增加

两种队列类型具有相似的曲线。随着我们增加飞行中限制,吞吐量会上升,直到我们看到这个值非常高,以至于不再有任何流量控制效果。两者在 20 和 200 之间都有最大的提升。10000 的限制对 1000 没有任何好处,只会增加端到端延迟。

仲裁队列实现了比镜像队列高得多的吞吐量,并且 95% 百分位延迟也更低。仲裁队列的 99.9% 百分位延迟会达到镜像队列的延迟水平,此时所有百分位都聚集在同一值附近。

在我们的例子中,因为代理和负载生成器都在同一个可用区,所以网络延迟非常低。在网络延迟更高的场景中,我们将继续看到更高的飞行中限制带来的巨大好处。

最后,请记住,如果我们的消息速率是 1000 条消息/秒,那么所有飞行中限制都会看起来一样。所以,如果您远未达到队列吞吐量的限制,那么这些设置不一定会起作用。

基准测试 #2 - 1000 飞行中限制、增加预取、确认间隔 1

这个测试与其他测试略有不同。其他测试是动态更改负载生成器行为的单次运行。在这个测试中,我们为每个设置使用单独的运行。我们必须这样做,因为您会看到预取值为 1 会使消耗速率如此之慢,以至于队列快速填满并对测试的后续阶段产生负面影响。因此,我们对每个预取设置都进行了完全独立的运行。

镜像队列

Fig 3. Mirrored queue with increasing consumer prefetch.
图 3. 镜像队列,消费者预取增加。

仲裁队列

Fig 4. Quorum queue with increasing consumer prefetch.
图 4. 仲裁队列,消费者预取增加。

预取值为 1,加上快速的发布者,对两种队列类型都不利,但仲裁队列表现尤其糟糕。仲裁队列在预取值为 1 和 10 时,消费者吞吐量非常低,但我们也看到随着时间的推移,发布速率下降,队列填满。

实际上,在前两次测试(预取 1 和 10)中,仲裁队列达到了大约 400 万条消息。我们知道,当仲裁队列的消息量达到数百万条时,它的速度会稍有下降。

从预取值为 100 开始,我们开始达到最高吞吐量,因为 RabbitMQ 消费者通道不必频繁阻塞(等待确认消息)。如我们下面看到的,设置高预取值不会影响端到端延迟(对于预取值为 100、1000、10000)。

Fig 5. End-to-end latency for both queue types with 100, 1000 and 10000 prefetch.
图 5. 两种队列类型预取值为 100、1000 和 10000 时的端到端延迟。

预取值不一定会增加延迟,但飞行中限制可以增加延迟的原因是,有了飞行中限制,我们正在对入口进行速率限制,避免在代理中缓冲;而预取仅影响已在飞行中的消息。消息是在代理中缓冲还是在客户端中缓冲,都不会影响延迟,尤其是在单个消费者测试中。在多个消费者测试中,可能会有影响。

关于端到端延迟的一些细微差别

当然,上述内容是基于端到端延迟是从发布者发送消息的那一刻到消费者接收消息的那一刻。在您的系统中,端到端延迟可能从更早的节点开始。因此,对发布者进行速率限制可以从 RabbitMQ 的角度减少延迟,但不一定能减少您的整个系统的延迟。当它确实会影响您整个系统的端到端延迟时,是因为 RabbitMQ 过载并明显减慢了速度。

基准测试 #3 - 1000 飞行中限制、1000 预取、增加确认间隔

我们又回到了动态更新设置,因为我们会发现,虽然确认间隔确实会影响吞吐量,但它不像预取那样影响吞吐量(根本不接近!)。使用确认间隔为 1 是可以的,您仍然会获得良好的吞吐量,所以如果您已经这样做并且不想要 multiple 标志使用的复杂性,那么继续即可。

但是,我们将在接下来的内容中看到,如果您想要榨取最后一丝性能,multiple 标志的使用会很有帮助。

镜像队列

Fig 6. Mirrored queue with increasing ack interval
图 6. 镜像队列,确认间隔增加

仲裁队列

Fig 7. Quorum queue with increasing ack interval
图 7. 仲裁队列,确认间隔增加

两种队列类型都发现在从确认间隔 1 切换到 10 时吞吐量有最大提升。之后峰值约为 50-100。这分别是预取的 5% 和 10%。根据经验法则,这通常是确认间隔的最佳选择。

当您超过预取的 25%-30% 时,镜像队列的吞吐量往往会下降,并在超过 50% 后迅速下降。在此测试中,仲裁队列在高达 50% 的范围内保持平坦。

基准测试 #4 - 无确认和应答

在这些测试中,我们将不使用发布者确认,并且消费者将使用自动应答模式(这意味着代理在传输消息后立即将其视为已送达)。

镜像队列

Fig 8. Mirrored queue without publisher confirms or consumer acks
图 8. 镜像队列,无发布者确认或消费者应答

仲裁队列

Fig 9. Quorum queue without publisher confirms or consumer acks
图 9. 仲裁队列,无发布者确认或消费者应答

与使用确认和应答相比,我们在这项测试中看不到吞吐量的任何好处。事实上,我们看到的是端到端延迟的增加。对于镜像队列,95% 百分位的延迟从约 60 毫秒增加到约 1 秒。同样,对于仲裁队列,95% 百分位的延迟从约 50 毫秒增加到约 400 毫秒。

因此,我们不仅没有看到吞吐量的增加,反而看到了更差的延迟。当然,这是一个单队列的情况,随着我们添加更多队列和负载,情况只会变得更糟,正如我们将在下一篇文章中看到的。

对于非复制的经典队列,您肯定会看到确认/应答与无确认/应答之间的区别。这是因为如果没有复制,RabbitMQ 不需要做太多工作,所以确认和应答的开销就比较明显。当涉及复制时,情况并非如此,确认/应答的开销与之相比很小。

最终结论

此时,对于单个队列,结论是简单且有限的——也就是说,它们肯定适用于单个队列,并且很可能适用于多个队列,但不一定是压力系统。这就是为什么我们有后续文章,涵盖完全相同的设置,但针对一个系统进行压力测试。

低代理压力、单队列、高吞吐量结论

  1. 低的发布者飞行中限制相当于较低的吞吐量,因为发布者会施加自己的流量控制。更高的飞行中限制相当于更高的吞吐量,但到一定程度后收益就会停止。这个点完全取决于您的系统,并且随着您系统条件的改变而改变。
  2. 对于具有单个消费者的、高吞吐量的队列来说,低的预取可能非常糟糕。但在低吞吐量队列或有大量消费者的队列中,这不会是一个大问题(正如我们在下一篇文章中将看到的,我们将有数百个消费者)。
  3. 确认间隔为 1 是可以的,不必过于担心。但稍微增加它可能是有益的。预取的 10% 是一个不错的经验法则,但一如既往,这取决于您的系统和当地条件。
  4. 确认和应答对于数据安全是必要的,并且在使用复制队列时未使用它们并不会带来任何性能优势,反而会增加延迟。话虽如此,在这个单队列测试中,丢失确认和应答提供的额外流量控制并不是一个主要问题。
  5. 最后,单个仲裁队列的表现明显优于单个镜像队列。

所有这些测试都是关于尽可能快地发送/消费消息,将单个队列推向其极限。我们学到的信息很有用,但您可能不会处于这种情况,因此您可能会发现下一篇文章更有用。在那篇文章中,我们将探讨低负载和高负载场景,使用不同数量的队列和客户端,并查看这三个相同设置对仲裁队列和镜像队列的影响。对于压力测试,流量控制将变得更加重要,它将帮助压力系统优雅地降级,而不是“着火”。使用确认和应答的缺失可能会产生更大的影响。

© . This site is unofficial and not affiliated with VMware.