跳到主要内容

集群大小调整案例研究 – Quorum 队列第 2 部分

·12 分钟阅读
Jack Vanlightly

上一篇文章中,我们开始对使用 Quorum 队列的工作负载进行大小调整分析。我们专注于理想情况,即消费者能够跟上,这意味着没有队列积压,并且集群中的所有 Broker 都在正常运行。通过运行一系列基准测试,以不同的强度模拟我们的工作负载,我们确定了前 5 种集群大小和存储容量组合,以每 1000 条消息/秒/月的成本计算。

  1. 集群: 7 节点,8 vCPU (c5.2xlarge),gp2 SDD。成本: $54
  2. 集群: 9 节点,8 vCPU (c5.2xlarge),gp2 SDD。成本: $69
  3. 集群: 5 节点,8 vCPU (c5.2xlarge),st1 HDD。 成本: $93
  4. 集群: 5 节点,16 vCPU (c5.4xlarge),gp2 SDD。成本: $98
  5. 集群: 7 节点,16 vCPU (c5.4xlarge),gp2 SDD。成本: $107

还需要进行更多测试,以确保这些集群能够处理诸如 Broker 故障和在中断或系统减速期间累积大量积压等情况。

所有 Quorum 队列都使用以下属性声明

  • x-quorum-initial-group-size=3
  • x-max-in-memory-length=0

x-max-in-memory-length 属性强制 Quorum 队列在安全的情况下尽快从内存中删除消息体。您可以将其设置为更长的限制,这是最激进的设置 - 旨在避免在消费者无法跟上时以增加磁盘读取为代价来避免大量内存增长。如果没有此属性,消息体将始终保存在内存中,这可能会导致内存增长到触发内存警报的程度,从而严重影响发布速率 - 这是我们希望在本工作负载案例研究中避免的情况。

不利条件 - 应对滚动重启和 Broker 丢失

我们使用 Quorum 队列是因为我们关心数据和可用性。如果由于磁盘故障或由于我们需要重启作为紧急操作系统补丁的一部分而丢失 Broker,那么我们可以获得持续的可用性且不会丢失数据,但是我们能否保持 30k msg/s 的目标峰值速率?弹性不仅仅是不丢失数据和保持可用,还在于充分处理负载。

为此,我们再次运行完全相同的测试,但是在每个强度级别中途硬性终止一个 Broker。

Fig 1. One broker killed during each test
图 1. 每次测试期间杀死一个 Broker

在相同的镜像队列(一个主队列,一个镜像队列)测试中,当 Broker 被杀死时,我们看到吞吐量下降。使用 Quorum 队列,我们没有看到如此强烈的影响。

让我们看一下 30k msg/s 的目标速率周期。

Fig 2. 30k msg/s lost broker test
图 2. 30k msg/s 丢失 Broker 测试

我们在镜像队列中看到丢失 Broker 的巨大影响的原因是,当主队列或镜像队列丢失时,镜像队列会尝试通过在另一个 Broker 上创建新镜像来维持冗余级别。这会将相同数量的消息流量集中在更少的服务器上。Quorum 队列不会这样做。如果丢失了一个托管队列副本的 Broker,则该队列的成员资格不会更改。只要大多数队列副本(领导者、追随者)可用,队列就会继续运行。一旦 Broker 重新上线,其在该 Broker 上的追随者副本将再次开始被复制。

因此,复制流量不会集中在更少的服务器上,只有客户端流量会。Quorum 队列还得益于这样一个事实:如果消费者恰好连接到托管他们想要消费的队列的追随者副本的 Broker,那么他们将直接从该副本读取 - 无需从托管领导者的 Broker 代理消息到消费者连接到的 Broker。

镜像队列尝试通过创建新镜像来维持冗余级别,但同步是阻塞的这一事实削弱了这一点。因此,许多管理员使用手动同步来避免在新镜像被复制到剩余 Broker 之间产生巨大的流量高峰。

最大的集群(7x16、7x8 和 9x8)没有看到丢失 Broker 的明显影响。故障转移到新领导者的速度很快,吞吐量与之前一样。

不利条件 - 消费者减速

在处理消息时,消费者通常需要与数据库或第三方 API 等其他系统交互。这些下游系统可能会由于负载过重或某种中断而减速,这会产生连锁反应,导致您的消费者减速。然后,这会导致队列中的消息数量增加,这也会影响发布者。当队列很小或为空时(为空是因为消息立即被消费),RabbitMQ 可提供最佳性能。

我们的要求规定,如果我们遇到消费者减速,发布应继续不受影响,即使在 30k msg/s 的目标峰值负载下也是如此。

在此测试中,每条消息的处理时间各不相同

  • 5 分钟,10 毫秒
  • 在 20 分钟内从 10 毫秒增长到 30 毫秒
  • 5 分钟,30 毫秒
  • 在 20 分钟内从 30 毫秒减少到 10 毫秒
  • 50 分钟,10 毫秒

由于这是一个高流量系统,积压会快速形成,因此消息积压可能会增长到数千万。我们将看到消费率呈 S 形,因为首先处理时间增加,然后减少,然后当消费者处理积压时,消费率超过发布率。

当消费率恢复但队列长度仍然很大时,我们可能会看到对发布者的影响。发布率可能会在一段时间内下降,直到积压被清除。性能更高的集群应该看不到影响或仅在短时间内受到影响。

我们将以三种不同的发布速率运行测试

  • 10k 消息/秒,在 100 个队列中分布 200 个消费者。最高消费率为 20k 消息/秒,然后在 30 毫秒处理时间时降至 6.6k 消息/秒。
  • 20k 消息/秒,在 100 个队列中分布 300 个消费者。最高消费率为 30k 消息/秒,然后在 30 毫秒处理时间时降至 10k 消息/秒。
  • 30k 消息/秒,在 100 个队列中分布 400 个消费者。最高消费率为 40k 消息/秒,然后在 30 毫秒处理时间时降至 13.3k 消息/秒。

Fig 3. Consumer slowdown test at publish rates 10k msg/s, 20k msg/s and 30k msg/s and quorum queues.
图 3. 在发布速率为 10k 消息/秒、20k 消息/秒和 30k 消息/秒以及 Quorum 队列下的消费者减速测试。

首先要注意的是,Quorum 队列比镜像队列表现好得多。使用镜像队列,没有集群能够在此测试中维持 30k 消息/秒的发布速率,但是使用 Quorum 队列,7x16 集群勉强能够处理它。

查看一些队列积压变得有多大的示例。

3x16 集群

Fig 4. Queue backlog size for the 3x36 cluster with quorum queues
图 4. 3x36 集群使用 Quorum 队列的队列积压大小

7x16 集群

Fig 5. Queue backlog size for the 7x16 cluster with quorum queues
图 5. 7x16 集群使用 Quorum 队列的队列积压大小

Fig 6. Memory usage and memory high watermark for the 7x16 cluster with quorum queues.
图 6. 7x16 集群使用 Quorum 队列的内存使用率和内存高水位线。

9x8 集群

Fig 7. Queue backlog size for the 9x8 cluster with quorum queues
图 7. 9x8 集群使用 Quorum 队列的队列积压大小

Fig 8. Memory usage and memory high watermark for the 9x8 cluster with quorum queues.
图 8. 9x8 集群使用 Quorum 队列的内存使用率和内存高水位线。

尽管 100 个队列的队列积压高达 2500 万,但内存使用率仍远低于高水位线(这是内存警报会阻止发布者的情况)。

所有集群都以 10k 消息/秒的速度处理了此测试。在 20k 消息/秒的速度下,只有两个集群处理了它(7x16 和 9x8)。7x16 集群是此测试的明显赢家,因为它成功处理了非常紧张的 30k 消息/秒测试,该测试产生了高达 2500 万条消息的积压。

您可以使用 PerfTest(从 2.12 及更高版本)运行类似的测试

bin/runjava com.rabbitmq.perf.PerfTest \
-H amqp://guest:guest@10.0.0.1:5672/%2f,amqp://guest:guest@10.0.0.2:5672/%2f,amqp://guest:guest@10.0.0.3:5672/%2f \
-z 1800 \
-f persistent \
-q 1000 \
-c 1000 \
-ct -1 \
-ad false \
--rate 100 \
--size 1024 \
--queue-pattern 'perf-test-%d' \
--queue-pattern-from 1 \
--queue-pattern-to 100 \
-qa auto-delete=false,durable=false,x-queue-type=quorum \
--producers 200 \
--consumers 200 \
--producer-random-start-delay 30 \
-vl 10000:300 \
-vl 11000:60 -vl 12000:60 -vl 13000:60 -vl 14000:60 -vl 15000:60 -vl 16000:60 -vl 17000:60 -vl 18000:60 -vl 19000:60 \
-vl 20000:60 -vl 21000:60 -vl 22000:60 -vl 23000:60 -vl 24000:60 -vl 25000:60 -vl 26000:60 -vl 27000:60 -vl 28000:60 -vl 29000:60 \
-vl 30000:300 \
-vl 29000:60 -vl 28000:60 -vl 27000:60 -vl 26000:60 -vl 25000:60 -vl 24000:60 -vl 23000:60 -vl 22000:60 -vl 21000:60 -vl 20000:60 \
-vl 19000:60 -vl 18000:60 -vl 17000:60 -vl 16000:60 -vl 15000:60 -vl 14000:60 -vl 13000:60 -vl 12000:60 -vl 11000:60 -vl 10000:60 \
-vl 10000:3000

不利条件 - 发布速率峰值超过消费者容量

与消费者减速类似,我们最终会遇到发布速率超过消费速率导致消息积压的情况。但是这次是由发布速率的巨大峰值引起的,我们的后端系统无法处理该峰值。吸收发布速率的峰值是选择消息队列的原因之一。您无需扩展后端系统来处理峰值负载(这可能很昂贵),而是允许消息队列吸收额外的流量。然后,您可以在一段时间内处理积压。

在此测试中,我们将处理时间保持在 10 毫秒,但先增加发布速率,然后再降低它

  • 5 分钟,基本速率
  • 在 20 分钟内从基本速率增长到峰值
  • 5 分钟,峰值。
  • 在 20 分钟内从峰值降低到基本速率
  • 50 分钟,基本速率

我们将运行三个测试

  • 10k 消息/秒基本发布速率,20k 消息/秒峰值。200 个消费者,最高消费率为 13k 消息/秒。
  • 20k 消息/秒基本发布速率,30k 消息/秒峰值。300 个消费者,最高消费率为 23k 消息/秒。
  • 30k 消息/秒基本发布速率,40k 消息/秒峰值。400 个消费者,最高消费率为 33k 消息/秒。

Fig 9. 10k msg/s base rate, 20k msg/s peak with up to 7k msg/s consumer rate deficit.
图 9. 10k 消息/秒基本速率,20k 消息/秒峰值,消费者速率不足高达 7k 消息/秒。

除了 3x16 和 5x8 集群外,所有集群都设法达到了 20k 消息/秒的发布速率峰值。

Fig 10. 20k msg/s base rate, 30k msg/s peak with up to 7k msg/s consumer rate deficit.
图 10. 20k 消息/秒基本速率,30k 消息/秒峰值,消费者速率不足高达 7k 消息/秒。

只有较大的 5x16、7x18 和 9x8 集群设法达到了 30k 消息/秒的峰值。

Fig 11. 30k msg/s base rate, 40k msg/s peak with up to 7k msg/s consumer rate deficit.
图 11. 30k 消息/秒基本速率,40k 消息/秒峰值,消费者速率不足高达 7k 消息/秒。

7x16 集群勉强达到了 40k 消息/秒的峰值,并看到其消息积压接近 600 万条消息,但它仍然处理了它。

您可以使用 PerfTest 运行类似的测试

bin/runjava com.rabbitmq.perf.PerfTest \
-H amqp://guest:guest@10.0.0.1:5672/%2f,amqp://guest:guest@10.0.0.2:5672/%2f,amqp://guest:guest@10.0.0.3:5672/%2f \
-z 1800 \
-f persistent \
-q 1000 \
-ct -1 \
-ad false \
-c 1000 \
--size 1024 \
--queue-pattern 'perf-test-%d' \
--queue-pattern-from 1 \
--queue-pattern-to 100 \
-qa auto-delete=false,durable=false,x-queue-type=quorum \
--producers 200 \
--consumers 200 \
--producer-random-start-delay 30 \
--consumer-latency 10000 \
-vr 100:300 \
-vr 102:60 -vr 104:60 -vr 106:60 -vr 108:60 -vr 110:60 -vr 112:60 -vr 114:60 -vr 116:60 -vr 118:60 -vr 120:60 \
-vr 122:60 -vr 124:60 -vr 126:60 -vr 128:60 -vr 130:60 -vr 132:60 -vr 134:60 -vr 136:60 -vr 138:60 -vr 140:60 \
-vr 142:60 -vr 144:60 -vr 146:60 -vr 148:60 -vr 150:60 \
-vr 148:60 -vr 146:60 -vr 144:60 -vr 142:60 -vr 140:60 -vr 138:60 -vr 136:60 -vr 134:60 -vr 132:60 -vr 130:60 \
-vr 128:60 -vr 126:60 -vr 124:60 -vr 122:60 -vr 120:60 -vr 118:60 -vr 116:60 -vr 114:60 -vr 112:60 -vr 110:60 \
-vr 108:60 -vr 106:60 -vr 104:60 -vr 102:60 -vr 100:60 \
-vr 100:3000

不利条件测试 - 结论

在执行理想条件测试后,我们有许多集群可以处理峰值负载,因此我们最终得到了按每 1000 条消息/秒/月的成本排名的前 5 名集群排行榜。现在,在运行不利条件测试后,我们从原始集合中缩小到两个潜在选项。7x16 是唯一可以处理所有测试的集群,但如果您也对成本敏感,那么更便宜的 9x8 集群也接近通过积压测试。

  • 集群: 7 节点,16 vCPU,gp2 SSD。成本: 每 1000 条消息/秒 $104
  • 集群: 9 节点,8 vCPU,gp2 SDD。成本: 每 1000 条消息/秒 $81

在理想条件下,扩展较小的 VM 为我们带来了最佳的最高吞吐量和成本效益。但是,考虑到不利条件,7x16 是最好的全能型集群。

对于此工作负载,我将选择 Quorum 队列(使用 gp2 卷)而不是镜像队列,因为它们在消息积压事件中具有继续处理入口速率的卓越能力。当然,选择 Quorum 队列还有其他原因,例如

  • 更好的数据安全性
  • 在处理滚动重启时更高的可用性

Quorum 队列案例研究要点

要点与镜像队列案例研究相同:不要只在理想条件下运行测试。确保将不利条件测试纳入您的方法论中,否则您可能会发现您的集群在最需要时无法处理您的工作负载。系统在重负载下更容易遭受此类问题,因此请在您预期的峰值负载水平及以上进行测试。

底线是 RabbitMQ 可以很好地处理 Broker 丢失,但它更难处理的是队列积压。我们的顶级集群,7x16 和 9x8 配置在理想条件下达到 65-70k 消息/秒,但在我们抛出的最不利条件下仅达到 20-30k 消息/秒。我说只有 20-30k 消息/秒,但这相当于每天 17-25 亿条消息,高于 RabbitMQ 的大多数用例。

最后...这是一个特定的工作负载,请查看第一篇文章中的其他建议,这些建议可能适用于其他工作负载和场景。

© . All rights reserved.