跳到主要内容

仲裁队列和流量控制 - 压力测试

·阅读时长 23 分钟
Jack Vanlightly

上一篇文章 中,我们对单个队列进行了一些简单的基准测试,以了解流水线发布者确认和消费者确认对流量控制的影响。

具体来说,我们着眼于

  • 发布者:限制正在传输的消息数量(已发送但等待确认的消息)。
  • 消费者:预取(代理在通道上允许的正在传输消息数量)。
  • 消费者:确认间隔(多重标记使用)。

不出所料,我们发现当我们限制发布者和代理同时传输的消息数量时,吞吐量很低。当我们提高这个限制时,吞吐量会增加,但只增加到一定程度。此后,我们发现吞吐量不再增加,反而延迟会增加。我们还发现,允许消费者使用多重标记对吞吐量有益。

在这篇文章中,我们将探讨这三个相同设置,但使用多个客户端、多个队列和不同数量的负载,包括压力测试。我们将看到,发布者确认和消费者确认在流量控制中发挥作用,有助于防止代理过载。

在数据安全方面,客户端起着作用,它们必须正确使用确认和确认才能实现至少一次处理。同样,数千个客户端不应该期望用负载猛烈攻击代理,并且对结果不承担任何责任。

注意,这篇文章中有相当多的细节,所以在你开始阅读之前,请确保你手边有饮料。

机械同情

我真的很喜欢“机械同情”这个词。当以低速驾驶赛车时,你可以做任何事。只有当你将汽车推到极限时,你才需要开始倾听它,感受振动并做出相应的调整,否则它会在比赛结束前坏掉。

同样,对于 RabbitMQ,如果负载较低,那么你可以做很多事情。你可能不会看到更改这三个设置或使用确认(至少对性能而言)的影响。只有当你将集群压力推到极限时,这些设置才会真正变得重要。

优雅降级

当你的系统接收的数据量超过其处理能力时,它应该怎么做?

  • 答案 1:接受所有数据,然后变成一堆燃烧的比特。
  • 答案 2:提供巨大高低吞吐量波动,延迟也差异巨大。
  • 答案 3:限制数据输入速率,并提供稳定吞吐量和低延迟。
  • 答案 4:优先考虑输入而不是输出,吸收数据就像高峰负载导致高延迟,但更好地跟上输入速率。

在 RabbitMQ 中,我们认为答案 3 和 4 是合理的期望,而没有人想要答案 1 和 2。

关于答案 4,何时高峰不是高峰?高峰何时会变成慢性?这样的系统如何优先考虑发布者而不是消费者?这是一个难以做出选择和难以实现的决定。RabbitMQ 更倾向于答案 3:限制发布者速率,并尽可能平衡发布和消费速率。

这归结为流量控制。

选择正确的正在传输限制和预取

如果你从未预期过高负载,那么决定很简单。我们在上一篇文章中看到,对于单个高吞吐量队列,你可以设置高正在传输限制、高预取,并且可以选择与消费者确认一起使用多重标记,你就可以正常运行。如果你负载较低,那么所有设置对最终吞吐量和延迟数字来说可能看起来都一样。

但是,如果你预计会出现高负载周期,并且拥有数百甚至数千个客户端,那么这还是一个好的选择吗?我所知道的回答这些问题的最佳方法是进行测试,进行许多测试,并使用各种参数。

因此,我们将进行一系列基准测试,使用不同的

  • 发布者数量
  • 队列数量
  • 消费者数量
  • 发布速率
  • 正在传输限制
  • 预取和确认间隔

我们将测量吞吐量和延迟。正在传输限制将是每个发布者目标速率的百分比,百分比范围从 1% 到 200%。例如,如果每个发布者的目标速率为 1000

  • 1% 正在传输限制 = 10
  • 5% 正在传输限制 = 50
  • 10% 正在传输限制 = 100
  • 20% 正在传输限制 = 200
  • 100% 正在传输限制 = 1000
  • 200% 正在传输限制 = 2000

与上一篇文章一样,我们将测试镜像队列和仲裁队列。镜像队列使用一个主节点加一个镜像节点(复制因子为 2),仲裁队列使用一个领导者和两个跟随者(复制因子为 3)。

所有测试都使用 RabbitMQ 3.8.4 的 Alpha 版本,该版本改进了仲裁队列内部机制以处理高负载。此外,我们将谨慎使用内存,并将仲裁队列的 x-max-in-memory-length 属性设置为较低的值。这使仲裁队列的行为类似于延迟队列,它将在安全的情况下立即将消息体从内存中移除,并且队列长度已达到此限制。如果没有此限制,仲裁队列会将所有消息保存在内存中。如果消费者跟不上,这可能会降低性能,因为会有更多磁盘读取操作,但它是一个更安全、更保守的配置。当我们对系统施加压力时,这一点会变得很重要,因为它避免了内存大幅波动。在这些测试中,它被设置为 0,这是最激进的设置。

所有测试都在具有 16 个 vCPU(Cascade Lake/Skylake Xeon)和 SSD 的 3 节点集群上进行。

基准测试

  1. 20 个发布者,1000 个消息/秒,10 个队列,20 个消费者,1kb 消息
  2. 20 个发布者,2000 个消息/秒,10 个队列,20 个消费者,1kb 消息
  3. 500 个发布者,30 个消息/秒,100 个队列,500 个消费者,1kb 消息
  4. 500 个发布者,60 个消息/秒,100 个队列,500 个消费者,1kb 消息
  5. 1000 个发布者,100 个消息/秒,200 个队列,1000 个消费者,1kb 消息

基准测试 #1:20 个发布者,每个发布者 1000 个消息/秒,10 个队列,20 个消费者

总目标速率为 20000 个消息/秒,对于此数量的客户端和队列,这在所选硬件上集群的总吞吐量限制范围内。这种负载对于该集群来说是可以承受的。

我们有两个测试

  1. 没有发布者确认
  2. 确认,正在传输限制为目标发送速率的百分比:1%(10)、2%(20)、5%(50)、10%(100)、20%(200)、100%(1000)。

没有确认的镜像队列

Fig 1. 20 publishers (1000 msg/s), 10 mirrored queues, 20 consumers without publisher confirms
图 1. 20 个发布者(1000 个消息/秒),10 个镜像队列,20 个消费者,没有发布者确认

集群没有受到发布者比其处理能力更强力的驱动。我们得到了与目标速率匹配的平稳吞吐量,延迟低于 1 秒。

有确认的镜像队列

Fig 2. 20 publishers (1000 msg/s), 10 mirrored queues, 20 consumers with publisher confirms and different in-flight limits
图 2. 20 个发布者(1000 个消息/秒),10 个镜像队列,20 个消费者,有发布者确认,并且具有不同的正在传输限制

在这种负载级别下,所有正在传输设置的行为都相同。我们还没有达到代理的限制。

没有确认的仲裁队列

Fig 3. 20 publishers (1000 msg/s), 10 quorum queues, 20 consumers without publisher confirms
图 3. 20 个发布者(1000 个消息/秒),10 个仲裁队列,20 个消费者,没有发布者确认

匹配目标速率,延迟低于 1 秒。

有确认的仲裁队列

Fig 4. 20 publishers (1000 msg/s), 10 quorum queues, 20 consumers with publisher confirms and different in-flight limits
图 4. 20 个发布者(1000 个消息/秒),10 个仲裁队列,20 个消费者,有发布者确认,并且具有不同的正在传输限制

使用确认和低正在传输限制,仲裁队列的速率略低于目标速率,但在所有百分位数上都实现了 < 200ms。当我们提高正在传输限制时,目标速率会达到,并且曲线平滑,但延迟会增加,尽管仍然低于 1 秒。

结论

当发布速率在集群可以将数据传递给消费者的能力范围内时,使用低正在传输限制的确认可以提供最佳的端到端延迟,而没有确认或使用高正在传输限制的确认可以提供目标吞吐量,但延迟更高(尽管仍然低于 1 秒)。

基准测试 #2:20 个发布者,每个发布者 2000 个消息/秒,10 个队列,20 个消费者

总目标速率为 40000 个消息/秒,这在所选硬件上集群的吞吐量限制范围内或更高。这种负载对于该集群来说可能无法持续,但可能在峰值负载条件下出现。如果它持续存在,建议使用更大的硬件。

我们有三个测试

  1. 没有发布者确认
  2. 确认,正在传输限制为目标发送速率的百分比:1%(20)、2%(40)、5%(100)、10%(200)、20%(400)、100%(2000)。预取为 2000,确认间隔为 1。
  3. 与 2 相同,但消费者使用多重标记,确认间隔为 200(预取的 10%)。

没有确认的镜像队列

Fig 5. 20 publishers (2000 msg/s), 10 mirrored queues, 20 consumers without publisher confirms
图 5. 20 个发布者(2000 个消息/秒),10 个镜像队列,20 个消费者,没有发布者确认

发布者短暂地接近目标速率,但发布者和消费者速率都在较低速率下稳定下来,发布速率超过消费者速率。这会导致队列填满,延迟激增。如果这种情况持续存在,那么队列会变得很大,并对资源使用施加越来越大的压力。

有确认的镜像队列

Fig 6. 20 publishers (2000 msg/s), 10 mirrored queues, 20 consumers with publisher confirms and different in-flight limits.
图 6. 20 个发布者(2000 个消息/秒),10 个镜像队列,20 个消费者,有发布者确认,并且具有不同的正在传输限制。

有确认和多重标记的镜像队列

Fig 7. 20 publishers (2000 msg/s), 10 mirrored queues, 20 consumers with publisher confirms and different in-flight limits. Multiple flag usage by consumers.
图 7. 20 个发布者(2000 个消息/秒),10 个镜像队列,20 个消费者,有发布者确认,并且具有不同的正在传输限制。消费者使用多重标记。

现在确认确实起到了作用,对发布者施加了有效的反向压力。我们以 20(目标速率的 1%)的最低正在传输限制达到了峰值吞吐量(仍然远远低于目标)。端到端延迟很低,约为 20ms。但是,当我们提高正在传输限制时,少数队列开始填满,导致第 95 个百分位延迟激增。

我们发现,在高在途消息限制的情况下,使用 multiple 标志可以减少发布到消费速率的失衡,从而略微降低最糟糕的延迟。但在这个案例中,效果并不十分明显。

没有确认的仲裁队列

Fig 8. 20 publishers (2000 msg/s), 10 quorum queues, 20 consumers without publisher confirms.
图 8. 20 个发布者(2000 条消息/秒),10 个仲裁队列,20 个消费者,没有发布者确认。

当队列数量较少时,仲裁队列的性能往往优于镜像队列。在这里,我们看到达到了 40000 条消息/秒,因此不需要对发布者施加反压。

有确认的仲裁队列

Fig 9. 20 publishers (2000 msg/s), 10 quorum queues, 20 consumers with publisher confirms and different in-flight limits.
图 9. 20 个发布者(2000 条消息/秒),10 个仲裁队列,20 个消费者,有发布者确认,不同的在途消息限制。

有确认和使用 multiple 标志的仲裁队列

Fig 10. 20 publishers (2000 msg/s), 10 quorum queues, 20 consumers with publisher confirms and different in-flight limits, with consumers using the multiple flag.
图 10. 20 个发布者(2000 条消息/秒),10 个仲裁队列,20 个消费者,有发布者确认,不同的在途消息限制,消费者使用 multiple 标志。

仲裁队列再次实现了更高的吞吐量,我们甚至在 2000 的在途消息限制下达到了 40000 条消息/秒的目标速率。使用 multiple 标志带来了轻微的益处。

结论

没有使用发布者确认和在途消息限制带来的反压,镜像队列崩溃了。当发布者使用确认时,它们实际上对发布者施加了反压,在在途消息限制达到目标速率的 100% 之前,实现了低延迟,此时延迟再次开始飙升。需要注意的是,这个目标速率超过了镜像队列的容量,我们看到了反压的重要性。

当队列和发布者的数量相对较少时,仲裁队列可以实现比镜像队列更高的吞吐量。它们能够提供 40000 条消息/秒的吞吐量,因此使用确认或不使用确认对稳定性能并不关键。

使用 multiple 标志是有益的,但不是改变游戏规则。

基准测试 #3:500 个发布者,每个发布者 30 条消息/秒,100 个队列,500 个消费者

目标总速率为 15000 条消息/秒,这在所选硬件上集群的总吞吐量限制内。

我们有两个测试

  1. 没有发布者确认
  2. 有确认,在途消息限制为目标发送速率的百分比:6% (2),10% (3),20% (6),50% 12,100% (30),200% (60),不使用 multiple 标志。

没有确认的镜像队列

Fig 11. 500 publishers (30 msg/s), 100 mirrored queues, 500 consumers without publisher confirms.
图 11. 500 个发布者(30 条消息/秒),100 个镜像队列,500 个消费者,没有发布者确认。

有确认的镜像队列

Fig 12. 500 publishers (30 msg/s), 100 mirrored queues, 500 consumers with publisher confirms and different in-flight limits
图 12. 500 个发布者(30 条消息/秒),100 个镜像队列,500 个消费者,有发布者确认,不同的在途消息限制。

没有确认的仲裁队列

Fig 13. 500 publishers (30 msg/s), 100 quorum queues, 500 consumers without publisher confirms.
图 13. 500 个发布者(30 条消息/秒),100 个仲裁队列,500 个消费者,没有发布者确认。

有确认的仲裁队列

Fig 14. 500 publishers (30 msg/s), 100 quorum queues, 500 consumers with publisher confirms and different in-flight limits
图 14. 500 个发布者(30 条消息/秒),100 个仲裁队列,500 个消费者,有发布者确认,不同的在途消息限制。

在所有情况下,我们都匹配了目标速率。有确认和低在途消息限制的情况下,吞吐量出现了一些抖动,在更高的限制下抖动消失了。

随着我们增加在途消息限制,延迟逐渐上升。镜像队列的延迟超过了 1 秒,而仲裁队列的延迟保持在 1 秒以下。

再次,我们看到当集群在其容量范围内时,我们不需要确认作为反压机制(仅用于数据安全)。

基准测试 #4:500 个发布者,每个发布者 60 条消息/秒,100 个队列,500 个消费者

目标总速率为 30000 条消息/秒,这略高于该集群在所选硬件上针对此数量的客户端和队列的总吞吐量限制。这会给集群带来压力,并不是一个该集群应该承受的持续负载。

我们有三个测试

  1. 没有发布者确认
  2. 有确认,在途消息限制为目标发送速率的百分比:5% (3),10% (6),20% (12),50% (24),100% (60),200% (120),预取值为 60。
  3. 与 2 相同,但使用 multiple 标志,确认间隔为 6(预取值的 10%)。

没有确认的镜像队列

Fig 15. 500 publishers (60 msg/s), 100 mirrored queues, 500 consumers without publisher confirms.
图 15. 500 个发布者(60 条消息/秒),100 个镜像队列,500 个消费者,没有发布者确认。

没有确认的情况下,发布者短暂地管理目标速率,但消费者跟不上。吞吐量相当不稳定,一半队列的延迟接近 1 分钟,其余队列的延迟超过 2-3 分钟。

有确认的镜像队列

Fig 16. 500 publishers (60 msg/s), 100 mirrored queues, 500 consumers with publisher confirms and different in-flight limits.
图 16. 500 个发布者(60 条消息/秒),100 个镜像队列,500 个消费者,有发布者确认,不同的在途消息限制。

有确认和多重标记的镜像队列

Fig 17. 500 publishers (60 msg/s), 100 mirrored queues, 500 consumers with publisher confirms and different in-flight limits with multiple fag usage.
图 17. 500 个发布者(60 条消息/秒),100 个镜像队列,500 个消费者,有发布者确认,不同的在途消息限制,使用 multiple 标志。

有确认的情况下,我们获得了更稳定的吞吐量,消费者跟上了发布速率,因为发布者被其在途消息限制限制了速率。multiple 标志这次确实有所帮助,将我们的吞吐量提高了 5000 条消息/秒。请注意,只有 3% 的目标速率的在途消息限制提供了最佳性能。

没有确认的仲裁队列

Fig 18. 500 publishers (60 msg/s), 100 quorum queues, 500 consumers without publisher confirms.
图 18. 500 个发布者(60 条消息/秒),100 个仲裁队列,500 个消费者,没有发布者确认。

发布者达到了目标速率,但消费者跟不上,队列正在填满。这并不是一个可持续的状态。

有确认的仲裁队列

Fig 19. 500 publishers (60 msg/s), 100 quorum queues, 500 consumers with publisher confirms and different in-flight limits.
图 19. 500 个发布者(60 条消息/秒),100 个仲裁队列,500 个消费者,有发布者确认,不同的在途消息限制。

有确认和使用 multiple 标志的仲裁队列

Fig 20. 500 publishers (60 msg/s), 100 quorum queues, 500 consumers with publisher confirms and different in-flight limits and multiple flag usage.
图 20. 500 个发布者(60 条消息/秒),100 个仲裁队列,500 个消费者,有发布者确认,不同的在途消息限制,使用 multiple 标志。

有发布者确认的情况下,我们看到了更稳定的吞吐量,但确实存在锯齿状模式。我们可以一直将在途消息限制提高到目标速率的 100%,而不会出现问题,尽管延迟一直在上升。在 200% 时,发布速率超过了消费速率,队列开始填满。

结论

当集群超过其限制时,使用发布者确认和在途消息限制可以确保发布和消费速率平衡。即使发布者可以更快地发布,它们也会自我限制速率,RabbitMQ 可以在很长一段时间内提供可持续的性能。

在发布者、消费者和队列数量较多的情况下,镜像队列和仲裁队列的最大吞吐量已收敛到相似的数字。仲裁队列不再比镜像队列性能更好。我们在客户端和队列数量较少的情况下看到了更高的吞吐量。更少意味着更少的上下文切换,更少的随机 I/O,所有这些都更高效。

基准测试 #5:1000 个发布者,每个发布者 100 条消息/秒,200 个队列,1000 个消费者

这个负载远远超过了该集群的处理能力,目标总速率为 100000 条消息/秒,分布在 200 个队列上。超过 10 个队列,集群的最大吞吐量会随着队列数量的增加而下降。

如果该集群曾经遇到过这种负载,那么它应该只承受短时间。

我们有三个测试

  1. 没有确认
  2. 有确认,在途消息限制为目标发送速率的百分比:2% (2),5% (5),10% (10),20% (20),50% (50),100% (100),预取值为 100。
  3. 与 2 相同,但使用 multiple 标志,确认间隔为 10(预取值的 10%)。

没有确认的镜像队列

Fig 21. 1000 publishers (100 msg/s), 200 mirrored queues, 1000 consumers without publisher confirms.
图 21. 1000 个发布者(100 条消息/秒),200 个镜像队列,1000 个消费者,没有发布者确认。

发布者几乎达到了目标速率,但随后代理内部的缓冲区开始达到容量,吞吐量像石头一样直线下跌。依靠 TCP 反压,默认的基于信用的流控制设置,1000 个发布者发送的速度比集群能够处理的速度快,结果并不好。

每个信贷链中的参与者最初的信贷为 400,因此每个连接上的读取进程至少会接受 400 条消息,然后才会被阻塞。对于 1000 个发布者来说,这意味着在读取进程中缓冲了 400000 条消息。再加上通道和队列的缓冲区,以及所有传出的端口缓冲区等等,你可以看到代理如何能够吸收大量来自大量发布者的消息,然后被这些消息阻塞,甚至在 TCP 反压生效之前。

有确认的镜像队列

Fig 22. 1000 publishers (100 msg/s), 200 mirrored queues, 1000 consumers with publisher confirms and different in-flight limits.
图 22. 1000 个发布者(100 条消息/秒),200 个镜像队列,1000 个消费者,有发布者确认,不同的在途消息限制。

有确认和多重标记的镜像队列

Fig 23. 1000 publishers (100 msg/s), 200 mirrored queues, 1000 consumers with publisher confirms and different in-flight limits.
图 23. 1000 个发布者(100 条消息/秒),200 个镜像队列,1000 个消费者,有发布者确认,不同的在途消息限制。

发布者很想达到目标速率,但它们被有效地限制了速率。随着我们增加在途消息限制,我们看到了吞吐量略微增加,延迟大幅增加。最终,当我们达到目标速率的 200% 的在途消息限制时,这有点太多了,但发布者仍然受到限制。队列稍微积压了一些,吞吐量下降,变得非常不稳定。使用 multiple 标志有所帮助,它减少了下降,将延迟保持在 25 秒以下。

如果我们查看RabbitMQ 概览 Grafana 仪表板(为了显示这里略微修改),我们看到当在途消息限制较低时,待确认消息和待确认消费者确认消息的数量很少,但当我们达到 100% 的在途消息限制时,这些数字达到了 100000。因此 RabbitMQ 在内部缓冲了更多消息。消费者还没有达到他们的预取限制,尽管他们的峰值达到了 55000,而他们的总可能预取值为 100000。

Fig 24. RabbitMQ overview shows pending confirms and acks increasing inline with the in-flight limit.
图 24. RabbitMQ 概览显示待确认消息和确认消息随着在途消息限制的增加而增加。

没有确认的仲裁队列

Fig 25. 1000 publishers (100 msg/s), 200 mirrored queues, 1000 consumers without publisher confirms.
图 25. 1000 个发布者(100 条消息/秒),200 个镜像队列,1000 个消费者,没有发布者确认。

与镜像队列相同。TCP 反压不足以阻止过载。

有确认的仲裁队列

Fig 26. 1000 publishers (100 msg/s), 200 quorum queues, 1000 consumers with publisher confirms and different in-flight limits.
图 26. 1000 个发布者(100 条消息/秒),200 个仲裁队列,1000 个消费者,有发布者确认,不同的在途消息限制。

有确认和使用 multiple 标志的仲裁队列

Fig 27. 1000 publishers (100 msg/s), 200 quorum queues, 1000 consumers with publisher confirms and different in-flight limits.
图 27. 1000 个发布者(100 条消息/秒),200 个仲裁队列,1000 个消费者,有发布者确认,不同的在途消息限制。

从低在途消息限制切换到中等大小的在途消息限制时,仲裁队列的收益明显高于镜像队列。使用 multiple 标志,我们甚至接近了 35000 条消息/秒的吞吐量。在目标速率限制的 100% 时,事情开始出错,在 200% 时,情况变得非常糟糕。发布者领先,导致队列填满。此时,你真的需要将 x-max-in-memory-length 仲裁队列属性的值设置得很低。如果没有它,在这些情况下,内存使用量会非常快地激增,导致吞吐量发生巨大波动,因为内存警报会反复开启和关闭。

我们在即将发布的 3.8.4 版本中对仲裁队列在压力下的内存使用量进行了重大改进。所有这些测试都显示了这些工作的结果。在本帖的最后,我们将展示这个测试的 3.8.3 版本,以及它如何无法很好地处理这个压力测试。

在概览仪表板中,我们看到队列是如何填满的。消费者已经达到了他们的预取限制。

Fig 28. RabbitMQ overview shows quorum queue pending confirms and acks increasing inline with the in-flight limit.
图 28. RabbitMQ 概览显示仲裁队列的待确认消息和确认消息随着在途消息限制的增加而增加。

结论

两种队列类型都无法在没有发布者确认的情况下处理这个负载。每个集群都完全不堪重负。

有确认的情况下,镜像队列和仲裁队列实现了相同的吞吐量和延迟范围,直到在途消息限制达到 100% 和 200% 时,仲裁队列的性能更差。

镜像队列很好地处理了过载,即使在途消息限制很高。仲裁队列需要额外的帮助,即较低的在途消息限制,才能以较低的延迟实现稳定的吞吐量。

3.8.3 及更早版本怎么样?

所有仲裁队列测试都在 3.8.4 的 alpha 版本上运行,为了展示即将发布的 3.8.4 版本的性能。但你们其他人将使用 3.8.3 及更早版本。那么,你能期待什么呢?

3.8.4 版本的改进包括:

  • 段写入的高吞吐量能力。消息首先写入 WAL,然后写入段文件。在 3.8.3 版本中,我们发现段写入器在高负载、高队列数的情况下成为了瓶颈,导致高内存使用率。3.8.4 版本引入了并行段写入,彻底解决了这个瓶颈。
  • 对仲裁队列的默认配置值进行了负载测试,我们发现一些更改导致在高负载下吞吐量更稳定。具体来说,我们将 quorum_commands_soft_limit 从 256 更改为 32,将 raft.wal_max_batch_size 从 32768 更改为 4096。

如果您使用的是 3.8.3 版本,好消息是现在滚动升级很容易执行,但如果您无法升级,请尝试上述配置。但是,您仍然可能遇到段写入器的瓶颈。

以下是 3.8.3 版本(应用了配置更改)的基准测试 #5,运行时间更长。

3.8.3 版本基准测试 #5

Fig 29. 3.8.3 sees large peaks and troughs caused by memory alarms.
图 29. 3.8.3 版本出现由内存警报导致的大量峰值和谷值。

3.8.3 版本的主要区别在于,随着我们增加飞行中限制,段写入器落后,内存不断增长,直到内存警报触发。发布者被阻塞,然后消费者不受限制地与发布者竞争,将它们的确认信息写入复制日志。消费速率达到高达 90k 条消息/秒的短峰值,直到队列清空、内存下降和警报停用,然后周而复始地重复。

我们可以从概览仪表盘中看到。3.8.4 alpha 版本的内存增长随着飞行中限制的增加而缓慢增加。

Fig 30. The 3.8.4 alpha sees stable memory growth as the in-flight limit increases.
图 30. 3.8.4 alpha 版本的内存增长随着飞行中限制的增加而稳定。

3.8.3 版本反复触发内存警报。

Fig 31. 3.8.3 hits memory alarms repeatedly under heavy load from a 1000 publishers.
图 31. 3.8.3 版本在来自 1000 个发布者的重负载下反复触发内存警报。

即使飞行中限制很低,这个来自 1000 个发布者的重负载对于段写入器来说也过于庞大,它在测试早期就接近内存警报。

因此,如果您有大量的发布者和队列数,并且负载定期出现超出限制的峰值,那么请考虑在 3.8.4 版本发布后升级。

最终结论

首先,如果您使用的是复制队列(镜像或仲裁),那么从数据安全角度来看,不使用发布者确认是非常不明智的。消息传递无法保证,因此请务必使用它们。

除了数据安全之外,这些测试表明确认也起到流量控制的作用。

一些关键的要点:

  • 当队列数量在每个内核 1-2 个左右时,仲裁队列比镜像队列可以提供更高的吞吐量。
  • 在发布者和队列数量少的情况下,您可以几乎做任何事。对于镜像和仲裁队列(不使用确认),TCP 回压可能就足够了。
  • 在发布者和队列数量多、负载高的情况下,TCP 回压就不够用了。我们必须使用发布者确认,以便发布者可以自我速率限制。
  • 在发布者和队列数量多、负载高的情况下,两种队列类型的性能大致相同。但在压力测试期间,仲裁队列需要通过更低的飞行中限制来获得一些额外的帮助。
  • 使用多个标记是有益的,但并不关键。
  • 无论您做什么,都不要在没有发布者确认的情况下让您的代理承受高负载!

那么最佳的飞行中限制是多少呢?我希望我已经说服您,这取决于具体情况,但作为经验法则,在发布者和代理之间的网络延迟较低的情况下,使用目标速率的 1% 到 10% 之间的限制是最佳的。对于发送速率高的发布者数量较少的情况,我们倾向于 10%,但对于数百个客户端的情况,我们倾向于 1%。这些数字可能会随着发布者和代理之间的延迟链接增加而增加。

关于消费者预取,所有这些测试都使用了目标发布速率的预取(每个发布者,而不是总共),但请记住,在这些测试中,发布者数量与消费者数量匹配。当使用多个标记时,确认间隔为预取值的 10%。使用多个标记是有益的,但如果您不使用它,也不会有什么大问题。

如果您目前使用的是镜像队列,并且您的工作负载更类似于基准测试 #5 而不是其他任何基准测试,那么建议您在 3.8.4 版本发布后进行升级。在负载下改进流量控制和弹性可能是一项持续的努力,但在很多情况下也是特定于工作负载的。希望您已经了解到,您可以通过使用确认来调整吞吐量和延迟,并获得所需的性能。

如果我不提到容量规划,那就失职了。确保 RabbitMQ 有足够的硬件来处理峰值负载是确保它能够提供可接受性能的最佳方法。但总会有一些意外负载、预算限制等等。

请记住,与所有此类基准测试一样,不要过分关注这些具体的数字。您的情况会有所不同。不同的硬件、不同的消息大小、扇出程度、不同的 RabbitMQ 版本、不同的客户端、框架等等。主要结论是,您不应该期望 RabbitMQ 在重负载下自行进行流量控制。这完全是关于机械同情。

本系列的下一篇文章将介绍从镜像队列迁移到仲裁队列。

© 2024 RabbitMQ. All rights reserved.