Quorum 队列和流控制 - 单队列基准测试
在上一篇文章中,我们介绍了什么是流控制,包括通用概念和 RabbitMQ 中可用的各种流控制机制。我们看到发布者确认和消费者确认不仅是数据安全措施,而且还在流控制中发挥作用。
在这篇文章中,我们将研究应用程序开发人员如何在单个队列的上下文中,使用发布者确认和消费者确认来获得安全性和高性能的平衡。
当 broker 过载时,流控制变得尤为重要。单个队列不太可能使您的 broker 过载。如果您发送大型消息,那么肯定会使您的网络饱和,或者如果您只有一个 CPU 核心,那么一个队列可能会将其耗尽。但是我们大多数人都使用 8 核、16 核或 30 核以上的机器。但是,分解确认和 ACK 对单个队列的影响很有意思。从那里,我们可以吸取教训,看看它们是否适用于更大的部署(下一篇文章)。
流水线和发布者确认
发布者可以根据正在传输的未确认消息的数量来限制自身速率。如果限制为 5,则发布者将发送 5 条消息,然后阻止,直到收到确认。如果收到单个确认,则发布者现在可以发送一条消息并再次阻止。如果收到三个确认,则发布者可以再发送三条,依此类推。
可以通过使用multiple标志将确认批量处理。这允许 broker 一次确认多条消息。如果 100 条消息正在等待确认,序列号为 1-100,则 broker 可以发送单个确认,其中设置了 multiple 标志,序列号为 100。这样可以减少发布者和 broker 之间的通信,从而提高效率。
这种流水线方法将产生最高和最稳定的吞吐量。您可以在教程 7,策略 #3 中找到有关如何执行此操作的代码示例。有 Java 版本和 C# 版本。相同的方法可以应用于其他语言。
流水线和消费者 ACK
RabbitMQ 采用流水线方法,其中其“正在传输的限制”是给定通道上的消费者预取(QoS)。如果没有预取,它将尽可能快地发送消息,直到没有更多消息要发送或由于客户端 TCP 缓冲区已满而应用 TCP 反压为止。这可能会使消费者过载,因此请使用预取!
当您的消费者发送确认时,就像 broker 向发布者发送确认一样。它允许将更多消息推送到通道中。
就像确认一样,消费者 ACK 也可以使用 multiple 标志。消费者可以选择单独确认每条消息或每 N 条消息确认一次。我们将其称为 ack interval(确认间隔)。确认间隔为 10 时,消费者将每确认 10 条消息,并使用 multiple 标志。
这可能是更复杂的代码,因为您还需要考虑到
- 如果最后 10 条消息包含 ACK、NACK 和拒绝的混合,那么您不能简单地执行带有 multiple 标志集的单个 ACK。
- 您可能希望对 ACK 之间的时间长度设置时间限制,以防消息传入缓慢,例如,每 10 条消息或最多 50 毫秒。
衡量正在传输的限制、预取和确认间隔的影响
查看影响的最佳方法是使用典型的集群运行一系列基准测试,并更改发布者确认的正在传输的限制、消费者预取和确认间隔。
所有基准测试均在 AWS 中运行,配置如下
- c5.4xlarge EC2 实例:16 个 vCPU(Cascade Lake/Skylake Xeon)、32 GB RAM、5gbit 网络、200 GB SSD(io1,IOPS 为 10000)
- 集群中的 3 个 broker
- 1 台相同规格的负载生成 EC2 机器 (c5.4xlarge)
- 1kb 消息
- 没有处理时间,因为这是单个发布者和单个消费者的纯吞吐量/延迟基准测试。
我们测试 Quorum 队列和镜像队列,以了解 Quorum 队列与其旧版本对应队列的不同之处。
镜像队列有一个 master 和一个 mirror,而 Quorum 队列使用三的复制因子(一个 leader + 两个 follower)。这并不完全是一场公平的战斗,镜像队列的复制因子为 2,Quorum 队列的复制因子为 3,但这些分别是常见配置。所有测试都使用 RabbitMQ 3.8.4 的 alpha 版本,其中包含用于处理高负载的新 Quorum 队列功能。
基准测试
- 增加正在传输的限制,预取 1000,确认间隔 1
- 1000 正在传输的限制,增加预取,确认间隔 1
- 1000 正在传输的限制,1000 预取,增加确认间隔
- 无确认,无 ACK
解释这些结果
规则
- 规则 1 - 这些是综合基准测试,使用特定版本的 RabbitMQ,使用云实例(这会引入各种可重复性问题)和特定的硬件配置。没有单一的基准测试结果,而是无限的。因此,不要关注具体数字,而要关注趋势和模式。
- 规则 2 - 这些结果使用的是 Java 客户端,而不是 Spring,也不是 Python 或任何其他语言或框架。但是,我们正在测试的内容应该适用于其他框架,因为它们必须使用相同的设置,它们如何使用这些设置可能在您的控制之下,也可能不在您的控制之下。
- 规则 3 - 使用这些不同的设置更改来试用您现有的代码,并亲自查看!
基准测试 #1 - 增加正在传输的限制,预取 1000,确认间隔 1
这是一个 30 分钟的基准测试,我们每 5 分钟增加一次正在传输的限制,值如下:1、5、20、200、1000、10000。对于低值,发布者将相当积极地限制自身速率,从而限制吞吐量,但随着限制的增加,我们应该看到吞吐量增加。
镜像队列

Quorum 队列

两种队列类型都具有相似的配置文件。随着我们增加正在传输的限制,吞吐量会上升,直到我们看到该级别太高而没有任何类型的流控制效果。两者都在 20 到 200 之间看到了最大的跳跃。10000 的限制没有超过 1000 的好处,所有发生的事情只是我们增加了端到端延迟。
Quorum 队列实现了比镜像队列更高的吞吐量,并且还具有更低的第 95 个百分位延迟。Quorum 队列的第 99.9 个百分位延迟达到了镜像队列的延迟,所有百分位数都聚集在相同的值附近。
在我们的案例中,由于 broker 和负载生成器都位于同一可用区中,因此网络延迟非常低。在更高的网络延迟场景中,我们将继续看到更高正在传输限制的巨大好处。
最后,请记住,如果我们的消息速率为 1000 msg/s,则所有正在传输的限制看起来都相同。因此,如果您远未达到队列吞吐量限制,那么这些设置不一定会发挥作用。
基准测试 #2 - 1000 正在传输的限制,增加预取,确认间隔 1
此测试与其他测试略有不同。其他测试是单个运行,我们在其中动态更改负载生成器的行为。在此测试中,我们为每个设置使用单独的运行。我们必须这样做,因为您会看到预取值为 1 会使消费速率非常慢,以至于队列快速填满,并对测试的后续阶段产生负面影响。因此,我们将每个预取设置作为完全隔离的运行来运行。
镜像队列

Quorum 队列

预取值为 1,加上快速发布者,对于两种队列类型来说效果都不好,但 Quorum 队列尤其糟糕。Quorum 队列在预取值为 1 和 10 时,消费者吞吐量非常低,但我们也看到发布速率随着时间的推移而下降,并且队列已满。
事实上,在前两个测试(预取 1 和 10)中,Quorum 队列达到了大约 400 万条消息。我们知道,Quorum 队列一旦达到数百万条消息,速度就会稍微减慢。
从预取值 100 开始,我们开始达到最高的吞吐量,因为 RabbitMQ 消费者通道不必经常阻止(等待 ACK 进入)。设置高预取值不会影响端到端延迟,我们将在下面看到(对于预取值 100、1000、10000)。

预取不一定会增加延迟,但正在传输的限制可能会增加延迟的原因是,使用正在传输的限制,我们正在限制入口,避免在 broker 中缓冲,而预取仅影响已在传输中的消息。消息是在 broker 中缓冲还是在客户端中缓冲并不影响延迟,尤其是在单个消费者测试中。在多个消费者测试中,仍然可能存在影响。
关于端到端延迟的一些细微差别
当然,以上内容的前提是端到端延迟是从发布者发送消息到消费者收到消息的那一刻开始的。在您的系统中,端到端延迟可能会在更早的时间点开始。因此,从 RabbitMQ 的角度来看,限制发布者速率可以减少延迟,但不一定减少您更广泛的系统的延迟。如果 RabbitMQ 过载并显着减速,那么它肯定会影响您更广泛的系统的端到端延迟。
基准测试 #3 - 1000 正在传输的限制,1000 预取,增加确认间隔
我们又回到了动态更新设置,因为我们将看到,虽然确认间隔确实会影响吞吐量,但它对吞吐量的影响不如预取(甚至远不及!)。使用确认间隔 1 是可以的,您仍然会获得良好的吞吐量,因此,如果这就是您已经做的,并且不想要 multiple 标志用法的复杂性,那么请继续。
但是接下来我们将看到,如果您想要每一丝性能,multiple 标志用法会有所帮助。
镜像队列

Quorum 队列

两种队列类型在从确认间隔 1 切换到 10 时,吞吐量都看到了最大的跳跃。之后,峰值在 50-100 左右。这分别是预取的 5% 和 10%。作为一般经验法则,这往往是确认间隔的最佳位置。
镜像队列往往会在您超过预取标记的 25-30% 后看到吞吐量降低,并且在超过 50% 后迅速下降。Quorum 队列在此测试中保持平稳,一直到 50%。
基准测试 #4 - 无确认和 ACK
在这些测试中,我们将不使用发布者确认,并且消费者将使用自动 ACK 模式(这意味着 broker 将在传输消息后立即将其视为已传递)。
镜像队列

Quorum 队列

如果我们将这些结果与使用确认和 ACK 的结果进行比较,我们看不到吞吐量的任何好处。实际上,我们看到的只是端到端延迟的增加。对于镜像队列,我们从第 95 个百分位数的约 60 毫秒变为约 1 秒。同样,对于 Quorum 队列,我们从第 95 个百分位数约 50 毫秒变为约 400 毫秒。
因此,我们不仅没有看到吞吐量的增加,而且还看到了更差的延迟。当然,这是一个单队列,随着我们添加更多队列和负载,情况只会变得更糟,我们将在下一篇文章中看到。
对于非复制的经典队列,您肯定会看到确认/ACK 与无确认/ACK 之间的区别。这是因为在没有复制的情况下,RabbitMQ 不必做太多工作,因此确认和 ACK 的开销是显着的。当涉及复制时,情况并非如此,确认/ACK 的开销相对较小。
最终结论
在这一点上,对于单个队列,结论是简单且有限的 - 也就是说,它们肯定适用于单个队列,并且可能适用于多个队列,但不一定适用于压力系统。这就是为什么我们有一篇后续文章,涵盖了完全相同的设置,但系统处于压力之下。
低 broker 压力、单队列、高吞吐量结论
- 低发布者正在传输的限制等同于较低的吞吐量,因为发布者施加了自己的流控制。较高的正在传输的限制等同于较高的吞吐量,但在某些时候,您将停止获得收益。该点在哪里完全取决于您的系统,并且会随着系统条件的变化而变化。
- 对于具有单个消费者的高吞吐量队列来说,低预取可能很糟糕。但是在低吞吐量队列或有许多消费者的队列中,它不会成为太大的问题(我们将在下一篇文章中看到,其中有数百个消费者)。
- 确认间隔为 1 是可以的,不必为此烦恼。但是稍微增加一点可能是有益的。高达预取的 10% 左右是一个很好的经验法则,但与往常一样,它取决于您的系统和本地条件。
- 确认和 ACK 对于数据安全是必要的,并且在复制队列中不使用它们不会为您带来任何性能提升,恰恰相反,它增加了延迟。也就是说,在此单队列测试中,确认和 ACK 施加的额外流控制的损失并不是一个主要问题。
- 最后 - 单个 Quorum 队列的性能明显优于单个镜像队列。
所有这些测试都是关于尽可能快地发送/消费消息,将单个队列推向极限。我们学到的是有启发性的,但您可能不处于这种情况,因此您可能会发现下一篇文章更有用。在那篇文章中,我们将研究低负载和高负载场景,使用不同数量的队列和客户端,但会看到这三个相同设置对 Quorum 队列和镜像队列的影响。对于压力测试,流控制将变得更加重要,它将帮助压力系统优雅地降级,而不是着火。预计不使用确认和 ACK 会产生更大的影响。