集群规模案例研究 – 镜像队列 第 2 部分
在上一篇文章中,我们开始了对使用镜像队列的工作负载的规模分析。我们专注于消费者能够跟上消息处理的理想情况,这意味着没有队列积压,并且集群中的所有代理都正常运行。通过运行一系列模拟不同强度工作负载的基准测试,我们确定了按每月每 1000 条消息/秒的成本计算的前 5 个集群规模和存储容量组合。
- 集群:5 个节点,8 个 vCPU,gp2 SSD。成本:58 美元
- 集群:7 个节点,8 个 vCPU,gp2 SSD。成本:81 美元
- 集群:5 个节点,8 个 vCPU,st1 HDD。成本:93 美元
- 集群:5 个节点,16 个 vCPU,gp2 SSD。成本:98 美元
- 集群:9 个节点,8 个 vCPU,gp2 SSD。成本:104 美元
还需要进行更多测试以确保这些集群能够处理诸如代理故障和在停机或系统速度下降期间累积的大量积压等情况。
不利条件 – 处理滚动重启和丢失的代理
如果代理宕机,我们的基于 gp2 SSD 的集群能否处理相同的负载?也许是虚拟机或磁盘发生故障,或者您需要执行紧急的操作系统补丁?我们确实需要确保在我们的黑色星期五促销高峰期,即使在出现故障的情况下,我们也能提供服务。
为此,我们再次运行完全相同的测试,但在每个强度级别进行过程中强制杀死一个代理。
一些集群的表现优于其他集群,但没有哪个集群能够在测试结束前避免在代理被杀死时出现吞吐量下降。较小的 3 个和 5 个代理集群在较低强度测试中看到吞吐量下降,而 7 个和 9 个代理集群仅在较高强度测试中才开始看到下降。
让我们看看我们的 30k 消息/秒目标速率周期。
您会注意到,5x16、7x16、7x8 和 9x8 集群完全恢复,而在另一端,3 个节点的集群出现最大下降。对于那些完全恢复的集群,下降幅度很小,但这是在*ha-sync-mode*设置为*manual*的情况下。如果您选择*automatic*,则恢复仍然会发生,但下降幅度更大,持续时间更长。
吞吐量下降的原因是,当镜像队列由于代理丢失而变得副本不足时,它将尝试在另一个代理上创建一个新的镜像(如果可以) - 保持相同的冗余级别。这会将相同数量的流量集中到较少的代理上。因此,如果您像本测试中一样使用复制因子 2(一个主节点,一个镜像),有三个代理并丢失一个,那么您将使其他两个代理的负载增加相当大的百分比。如果您使用*ha-mode=all*,则不会看到这种下降,因为没有代理可用于放置新的镜像。
但是,如果您有九个节点并丢失一个,那么负载增加将是微不足道的。
横向扩展在本轮中胜出。
不利条件 – 消费者速度下降
在处理消息时,消费者通常需要与其他系统(如数据库或第三方 API)交互。这些下游系统可能会因负载过重或某种类型的中断而速度下降,这会对消费者的速度产生连锁反应。然后,这会导致队列中的消息数量增加,这也会影响发布者。当队列很小或为空时,RabbitMQ 可以提供最佳性能(为空是因为消息会立即被消费)。
我们的需求规定,如果消费者速度下降,发布应不受影响,即使在 30k 消息/秒的目标峰值负载下也是如此。
在本测试中,每条消息的处理时间会发生变化
- 5 分钟,处理时间为 10 毫秒
- 在 20 分钟内,处理时间从 10 毫秒增加到 30 毫秒
- 5 分钟,处理时间为 30 毫秒
- 在 20 分钟内,处理时间从 30 毫秒减少到 10 毫秒
- 50 分钟,处理时间为 10 毫秒
由于这是一个高流量系统,积压可能会很快形成,因此消息积压可能会增长到数千万条。随着处理时间首先增加,然后减少,以及消费速度随后超过发布速度,因为消费者处理积压,我们将看到消费速度呈 S 形。
当消费速度恢复但队列长度仍然非常大时,这时我们可能会看到对发布者的影响。发布速度可能会在一段时间内下降,直到积压清除。性能更高的集群应该不会受到影响,或者只会受到短暂的影响。
我们将以三种不同的发布速度运行测试
- 10k 消息/秒,200 个消费者跨 100 个队列。最高消费速度为 20k 消息/秒,然后在 30 毫秒的处理时间时降至 6.6k 消息/秒。
- 20k 消息/秒,300 个消费者跨 100 个队列。最高消费速度为 30k 消息/秒,然后在 30 毫秒的处理时间时降至 10k 消息/秒。
- 30k 消息/秒,400 个消费者跨 100 个队列。最高消费速度为 40k 消息/秒,然后在 30 毫秒的处理时间时降至 13.3k 消息/秒。
查看队列积压增长有多大的示例。
3x16 集群
7x16 集群
队列积压增长相当大,但即使如此,我们也只达到 11GB 最大内存限制的 50%。我们使用服务器内存的 40% 作为默认内存高水位线。
9x8 集群
较小的 8 个 vCPU 实例可用的内存只有一半,高水位线为 6GB,但这些测试仍然只使用了大约一半的内存。
在 10k 消息/秒时,所有集群都能处理消费者速度下降和相关的积压。
在 20k 消息/秒时,只有 7x16 和 9x8 集群能够在发布速度下降的情况下处理它。7x8 非常接近于处理它。其他集群在队列积压仍然很高时,由于消费者和发布者之间存在竞争,导致发布速度下降。长队列效率较低,这既是因为磁盘使用量增加,也是因为内存数据结构。请注意,这是我们预期的峰值负载,但我们希望能够应对超过峰值负载的情况(在 30k 消息/秒时)。
但在 30k 消息/秒时,我们的集群都无法在消费者速度下降期间持续处理 30k 消息/秒。表现最佳的是 7x16 和 9x8 集群,它们的发布速度降低了大约 20-25 分钟。
因此,我们要么决定这足够好,要么需要使用 9x16 或 11x8 集群来进一步扩大规模。
9x16 集群可以处理 30k 消息/秒的负载,但发布速度略有波动。对于 8 个 vCPU 实例,看起来我们需要增加到 13 个或更多实例。这些都是大型集群,但这也是一个非常苛刻的工作负载。
您可以使用 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 \
--rate 100 \
--size 1024 \
--queue-pattern 'perf-test-%d' \
--queue-pattern-from 1 \
--queue-pattern-to 100 \
--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 消息/秒。
7x16、9x8、7x8 集群可以处理峰值,5x8 集群大部分可以处理,但发布速度偶尔会短暂下降。其他集群接近目标,但无法处理目标速率。
只有 7x16 和 9x8 集群可以处理它,但 5 个节点的集群很接近。
只有 7x16 集群达到了 40k 消息/秒的发布速度,但 9x8 集群很接近。7x16 的消息积压接近 700 万条,但它仍然可以处理。
您可以使用 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 \
-c 1000 \
--size 1024 \
--queue-pattern 'perf-test-%d' \
--queue-pattern-from 1 \
--queue-pattern-to 100 \
--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 个集群排行榜。现在,在运行不利条件测试后,我们从最初的集合中缩小到两个潜在选项
- 集群:7 个节点,16 个 vCPU,gp2 SSD。成本:每 1000 条消息/秒 104 美元
- 集群:9 个节点,8 个 vCPU,gp2 SSD。成本:每 1000 条消息/秒 81 美元
在理想情况下,扩展较小的虚拟机为我们提供了最佳的吞吐量和成本效益。但在考虑了弹性测试后,7x16 是最佳的全面解决方案。
当然,即使是 7x16 集群也难以应对 30k 消息/秒的消费者速度下降测试。因此,我们可能仍然需要考虑以下集群
- 集群:9 个节点,16 个 vCPU,gp2 SSD。成本:每 1000 条消息/秒 133 美元
- 集群:11 个节点,8 个 vCPU,gp2 SSD。成本:每 1000 条消息/秒 99 美元
镜像队列案例研究总结
注意不要只测试简单的场景,比如我们第一个快乐路径测试,其中发布速率恒定且消费速率固定——您只是在理想条件下对 RabbitMQ 进行规模调整。如果您需要 RabbitMQ 在面临困难时也能提供一定的吞吐量,那么您需要包含像我们在这篇文章中运行的那些测试。在负载较重的情况下,您更有可能遇到不利情况。由慢速消费者引起的队列积压在整个系统负载较重时更容易发生。同样,流量峰值也可能导致发布速率超过消费速率。因此,在峰值条件下和超出峰值条件下进行测试对于确保集群能够承受预期负载至关重要。
底线是 RabbitMQ 可以很好地处理代理丢失,它更难以处理的是队列积压。我们的顶级集群,7x16 和 9x8 配置在理想条件下达到 65-70k 条消息/秒,但在我们施加的最不利条件下仅达到 20k 条消息/秒。我说只有 20k 条消息/秒,但这相当于每天 17 亿条消息,这高于 RabbitMQ 的大多数用例。
最后……这是一个特定的工作负载,请查看第一篇文章中的其他建议,这些建议可能适用于其他工作负载和场景。