仲裁队列和流量控制 - 单队列基准测试
在上一篇文章中,我们介绍了流量控制的概念,包括通用概念和 RabbitMQ 中提供的各种流量控制机制。我们发现,发布者确认和消费者确认不仅是数据安全措施,而且在流量控制中也起着作用。
在本文中,我们将探讨应用程序开发人员如何使用发布者确认和消费者确认在单队列的背景下实现安全性和高性能的平衡。
当代理被过载时,流量控制变得尤为重要。单队列不太可能过载你的代理。如果你发送大型消息,那么当然,你可以饱和你的网络,或者如果你只有一个 CPU 内核,那么一个队列可能会将其最大限度地利用。但我们大多数人都使用 8、16 或 30 多个内核的机器。但分解确认和 ACK 对单队列的影响很有意思。从那里,我们可以学习经验,看看它们是否适用于更大规模的部署(下一篇文章)。
流水线和发布者确认
发布者可以根据未确认消息的飞行数量限制自己的速率。如果限制为 5,发布者将发送 5 条消息,然后阻塞,直到确认到达。如果收到一条确认,发布者现在可以发送一条消息并再次阻塞。如果收到三条确认,发布者可以发送三条消息,依此类推。
确认可以通过使用 *multiple* 标志进行批量处理。这允许代理一次性确认多条消息。如果 100 条消息正在等待确认,序列号为 1-100,代理可以发送一条带有 *multiple* 标志的确认,序列号为 100。这允许发布者和代理之间进行更少的通信,从而更高效。
这种流水线方法将产生最高且最稳定的吞吐量。你可以在教程 7 的策略 #3 中找到有关如何执行此操作的代码示例。有一个 Java 版本和一个 C# 版本。相同的方法可以应用于其他语言。
流水线和消费者 ACK
RabbitMQ 采用流水线方法,其中其“飞行中限制”是在给定通道上的消费者预取(QoS)。如果没有预取,它将尽快发送消息,直到没有更多消息要发送或由于客户端 TCP 缓冲区已满而应用了 TCP 反向压力。这可能会过载消费者,因此请使用预取!
当你的消费者发送确认时,就像代理向发布者发送确认一样。它允许将更多消息推送到通道。
就像确认一样,消费者 ACK 也可以使用 *multiple* 标志。消费者可以选择单独确认每条消息或每 N 条消息确认一次。我们称之为 *ACK 间隔*。如果 ACK 间隔为 10,消费者将使用 *multiple* 标志确认每 10 条消息。
这可能会使代码变得更加复杂,因为你还需要考虑
- 如果最后 10 条消息包含 ACK、NACK 和拒绝的混合,那么你不能简单地使用带有 *multiple* 标志的单次 ACK。
- 你可能希望在 ACK 之间的间隔时间长度上设置一个时间限制,以防消息缓慢到达,例如每 10 条消息或最多 50 毫秒。
测量飞行中限制、预取和 ACK 间隔的影响
查看影响的最佳方法是使用典型集群运行一系列基准测试,并更改发布者确认飞行中限制、消费者预取和 ACK 间隔。
所有基准测试都在 AWS 中运行,配置如下
- c5.4xlarge EC2 实例:16 个 vCPU(Cascade Lake/Skylake Xeon),32 GB RAM,5 Gbit 网络,200 GB SSD(io1,10000 IOPS)
- 集群中的 3 个代理
- 1 台相同规格的负载生成 EC2 机器(c5.4xlarge)
- 1 KB 消息
- 没有处理时间,因为这是一个单发布者和单消费者的纯吞吐量/延迟基准测试。
我们测试仲裁队列和镜像队列,以了解仲裁队列与旧队列有何不同。
镜像队列有一个主节点和一个镜像,仲裁队列使用复制因子 3(一个领导者 + 两个跟随者)。这不是公平的比较,镜像队列的复制因子为 2,仲裁队列的复制因子为 3,但它们分别是各自最常见的配置。所有测试都使用 RabbitMQ 3.8.4 的 alpha 版本,其中包含用于处理高负载的新仲裁队列功能。
基准测试
- 增加飞行中限制,预取 1000,ACK 间隔 1
- 1000 个飞行中限制,增加预取,ACK 间隔 1
- 1000 个飞行中限制,1000 个预取,增加 ACK 间隔
- 没有确认,没有 ACK
解释这些结果
规则
- 规则 1 - 这些是合成基准测试,使用特定版本的 RabbitMQ,云实例(这会引入各种可重复性问题)和特定硬件配置。没有单一的基准测试结果,而是无限的结果。所以不要关注具体数字,而是关注趋势和模式。
- 规则 2 - 这些结果使用 Java 客户端,而不是 Spring、Python 或任何其他语言或框架。但是,我们测试的内容应该适用于其他框架,因为它们必须使用相同的设置,它们如何使用这些设置可能不受你的控制。
- 规则 3 - 使用这些不同的设置更改尝试你的现有代码,并亲自看看!
基准测试 #1 - 增加飞行中限制,预取 1000,ACK 间隔 1
这是一个 30 分钟的基准测试,我们每 5 分钟增加一次飞行中限制,使用以下值:1、5、20、200、1000、10000。使用低值,发布者将非常积极地限制自己的速率,限制吞吐量,但随着限制的增加,我们应该看到吞吐量增加。
镜像队列
仲裁队列
两种队列类型的配置文件相似。随着我们增加飞行中限制,吞吐量会上升,直到我们看到该级别过高而没有流量控制效果。两者在 20 和 200 之间看到最大的跳跃。10000 的限制与 1000 没有任何区别,所有发生的事情是我们增加了端到端延迟。
仲裁队列的吞吐量远高于镜像队列,而且第 95 个百分位延迟也更低。仲裁队列的第 99.9 个百分位延迟达到了镜像队列的延迟水平,所有百分位都集中在同一个值附近。
在我们的案例中,因为代理和负载生成器都在同一个可用区,所以网络延迟非常低。在更高的网络延迟场景中,我们将继续看到更高的飞行中限制带来的巨大优势。
最后,请记住,如果我们的消息速率为 1000 消息/秒,那么所有飞行中限制看起来都一样。因此,如果你远未达到队列吞吐量限制,那么这些设置不一定能发挥作用。
基准测试 #2 - 1000 个飞行中限制,增加预取,ACK 间隔 1
此测试与其他测试略有不同。其他测试是单个运行,我们在其中动态更改负载生成器的行为。在此测试中,我们为每个设置使用单独的运行。我们必须这样做,因为你将看到,预取 1 使消耗速率非常慢,以至于队列很快填满并对测试的后续阶段产生负面影响。因此,我们将每个预取设置作为完全独立的运行执行。
镜像队列
仲裁队列
预取 1,加上快速发布者,对两种队列类型都不利,但仲裁队列尤其糟糕。仲裁队列在预取 1 和 10 时,消费者的吞吐量非常低,但我们还看到发布速率随着时间的推移而下降,因为队列已满。
事实上,在这前两个测试中(预取 1 和 10),仲裁队列达到了大约 400 万条消息。我们知道,仲裁队列在达到数百万条消息时会略微变慢。
从预取 100 开始,我们开始达到最高吞吐量,因为 RabbitMQ 消费者通道不必经常阻塞(等待 ACK 到达)。设置高预取不会影响端到端延迟,因为我们在下面看到(对于 100、1000、10000 的预取)。
预取不一定会增加延迟,而飞行中限制可能会增加延迟的原因是,使用飞行中限制,我们限制了入站速率,避免了在代理中进行缓冲,而预取只影响正在飞行的消息。消息是在代理中缓冲还是在客户端中缓冲都不会影响延迟,尤其是在单消费者测试中。在多消费者测试中,可能会产生影响。
关于端到端延迟的一些细微差别
当然,上述内容是基于端到端延迟,即从发布者发送消息到消费者收到消息的时刻开始。在你的系统中,端到端延迟可能会从更早的时刻开始。因此,限制发布者的速率可以减少从 RabbitMQ 角度来看的延迟,但不一定会减少整个系统的延迟。当它肯定会影响整个系统的端到端延迟时,是在 RabbitMQ 过载并明显变慢的情况下。
基准测试 #3 - 1000 个在途限制,1000 个预取,增加确认间隔
我们回到了动态更新设置,我们会发现,虽然确认间隔确实会影响吞吐量,但它并没有像预取那样影响吞吐量(甚至不接近!)。使用 1 的确认间隔是可以的,您仍然可以获得良好的吞吐量,因此,如果您已经这样做并且不想使用多个标志带来的复杂性,那就继续吧。
但我们接下来会看到,如果您想要获得所有性能,使用多个标志会有帮助。
镜像队列
仲裁队列
两种队列类型在将确认间隔从 1 切换到 10 时,吞吐量都会出现最大跳跃。之后峰值在 50-100 左右。分别为预取的 5% 和 10%。作为一般经验法则,这往往是确认间隔的最佳值。
镜像队列在超过预取标记的 25-30% 之后,吞吐量往往会下降,并且在超过 50% 之后会迅速下降。法定人数队列在本测试中保持平稳,一直到 50%。
基准测试 #4 - 无确认和确认
在这些测试中,我们将不使用发布者确认,并且消费者将使用自动确认模式(这意味着代理会在消息传输后立即将其视为已传送)。
镜像队列
仲裁队列
如果我们将这些结果与使用确认和确认进行比较,我们会发现吞吐量没有任何优势。事实上,我们只看到端到端延迟增加。对于镜像队列,我们从 95% 百分位数的约 60 毫秒变为约 1 秒。同样地,对于法定人数队列,我们从 95% 百分位数的约 50 毫秒变为约 400 毫秒。
因此,我们不仅没有看到吞吐量增加,而且还看到了更糟糕的延迟。当然,这只是一个队列,随着我们添加更多队列和负载,情况只会变得更糟,就像我们将在下一篇文章中看到的那样。
对于非复制的经典队列,您一定会看到确认/确认与无确认/确认之间的区别。这是因为在没有复制的情况下,RabbitMQ 不需要做太多工作,因此确认和确认的开销是显而易见的。当涉及到复制时情况并非如此,确认/确认的开销与之相比很小。
最终结论
在这一点上,对于单个队列,结论很简单且有限 - 也就是说,它们肯定适用于单个队列,并且很可能适用于多个队列,但不一定适用于压力系统。这就是为什么我们有一篇后续文章专门介绍相同的设置,但系统处于压力之下。
低代理压力,单个队列,高吞吐量结论
- 低发布者在途限制意味着更低的吞吐量,因为发布者会施加自己的流量控制。更高的在途限制意味着更高的吞吐量,但在某一点,你将不再获得收益。这一点在哪里完全取决于你的系统,并且会随着系统中条件的变化而变化。
- 低预取对于具有单个消费者的高吞吐量队列来说可能很糟糕。但在低吞吐量队列或存在大量消费者的队列中,它不会是一个大问题(就像我们将在下一篇文章中看到的那样,我们有数百个消费者)。
- 1 的确认间隔是可以的,不用担心。但稍微增加它会是有益的。大约预取的 10% 是一个好的经验法则,但一如既往,它取决于你的系统和本地条件。
- 确认和确认对于数据安全是必要的,并且在复制队列中不使用它们不会带来任何性能提升,恰恰相反,它会增加延迟。也就是说,在这个单个队列测试中,由于确认和确认带来的额外流量控制的损失并不是一个主要问题。
- 最后 - 单个法定人数队列的性能明显优于单个镜像队列。
所有这些测试都是关于尽可能快地发送/消费消息,将单个队列推向极限。我们学到的知识是有参考价值的,但你可能并不处于这种情况下,因此你可能会发现下一篇文章更有用。在那篇文章中,我们将探讨低负载和高负载场景,以及不同数量的队列和客户端,但看看这些相同的三个设置对法定人数队列和镜像队列的影响。对于压力测试,流量控制将变得更加重要,它将帮助压力系统平稳地降级,而不是陷入困境。预计不使用确认和确认的影响更大。