RabbitMQ 性能度量,第二部分
欢迎回来!上一次我们谈到了流量控制和延迟;今天让我们来谈谈不同的功能如何影响我们看到的性能。这里是一些简单的场景。和以前一样,它们都是围绕一个发布者和一个消费者尽可能快地发布的主题展开的。
一些简单的场景
第一个场景是最简单的——只有一个生产者和一个消费者。所以我们有一个基线。
当然我们希望产生令人印象深刻的数字。所以我们可以比那更快一点——如果我们不消费任何东西,那么我们可以更快地发布。
这使用了我们服务器上的几个核心——但不是全部。因此,为了获得最佳的吸引眼球的速率,我们启动了多个并行的生产者,它们都发布到空处。
当然,消费非常重要!因此,为了获得最大的消费速率,我们将发布到大量并行的消费者。
当然,在某种程度上,这种对大量数字的追求有点愚蠢,我们对相对性能更感兴趣。所以让我们恢复到一个生产者和一个消费者。
现在让我们尝试使用设置的 mandatory 标志进行发布。我们下降到非 mandatory 速率的约 40%。造成这种情况的原因是,我们发布到的通道不能再只是异步地将消息流式传输到队列;它会同步地与队列检查以确保它们仍然存在。(是的,我们可能可以使 mandatory 发布更快,但它并不被广泛使用。)
immediate 标志为我们提供了几乎完全相同的性能下降。这并不奇怪——它必须对队列进行相同的同步检查。
抛弃很少使用的 mandatory 和 immediate 标志,让我们尝试为已交付的消息开启确认。与不确认交付相比,我们仍然看到性能下降(毕竟服务器必须做更多簿记工作),但并不明显。
现在我们也开启发布确认。性能下降了一点,但我们仍然超过了既没有确认也没有确认的速率的 60%。
最后,我们启用消息持久化。速率变得低很多,因为我们也把所有这些消息都扔到磁盘上了。
消息大小
值得注意的是,到目前为止,我们发送的所有消息都只有几字节长。有两个原因:
- RabbitMQ 执行的大量工作是按消息进行的,而不是按消息的字节数进行的。
- 查看大数字总是很不错。
但在现实世界中,我们经常希望发送更大的消息。所以让我们看看下一张图表
1 -> 1 发送速率消息大小
在这里(同样),我们尽可能快地发送未确认/未确认的消息,但这次我们改变了消息大小。我们可以看到(当然),随着大小的增加,消息速率下降得更多,但随着路由开销越来越小,实际发送的字节数会增加。
那么消息大小如何影响横向扩展?让我们在不同的消息大小下改变生产者的数量。为了换个花样,在这个测试中,我们将没有任何消费者。
n -> 0 发送消息速率与生产者数量的关系图,显示各种消息大小
n -> 0 发送字节速率与生产者数量的关系图,显示各种消息大小
在这些测试中,我们可以看到,对于小消息来说,只需要几个生产者就可以达到我们能够发布的最高消息数,但对于大消息来说,我们需要更多的生产者才能利用可用的带宽。
另一个经常令人困惑的问题是带有预取数量的消费者的性能。RabbitMQ(准确地说,是 AMQP)默认情况下会将所有可能的消息发送到任何看起来准备接受消息的消费者。每个通道中这些未确认消息的最大数量可以通过设置预取数量来限制。但是,小的预取数量会损害性能(因为我们可能需要等待确认到达才能发送更多消息)。
所以让我们来看看预取数量,顺便也考虑一下从单个队列消费的消费者的数量。此图表包含一些故意夸张的极端情况。
1 -> n 接收速率与消费者数量/预取数量的关系图
首先要注意的是,微小的预取数量确实会损害性能。注意预取 = 1 和预取 = 2 之间的巨大性能差异!但我们也进入了收益递减——注意预取 = 20 和预取 = 50 之间的差异很难看出来,而预取 = 50 和预取 = 10000 之间的差异几乎看不见。当然,这是因为对于我们特定的网络连接来说,预取 = 50 已经确保我们在等待确认时永远不会使消费者处于饥饿状态。当然,此测试是在低延迟链接上运行的——更高延迟的链接将从更高的预取数量中受益。
第二点要注意的是,当我们只有少量消费者时,添加一个消费者会提高性能(我们获得了更多并行性)。而且对于微小的预取数量来说,即使增加到大量的消费者也会带来益处(因为每个独立的消费者在很大程度上都处于饥饿状态)。但是,当我们有更大的预取数量时,增加消费者的数量并没有那么有帮助,因为即使少量消费者也可以一直保持忙碌,从而最大限度地利用我们的队列,但是我们拥有的消费者越多,RabbitMQ 需要做的跟踪所有这些消费者的工作就越多。
大型队列
到目前为止,我们所看的所有示例都有一个共同点:实际上只有很少的消息被排队。总的来说,我们关注的是消息与生产的速度一样快地被消费的场景,因此每个队列的平均长度为 0。
那么队列变大时会发生什么?当队列很小(ish)时,它们将完全驻留在内存中。持久化消息也将写入磁盘,但只有在代理重启时才会再次读取。
但是,当队列变大时,它们将被分页到磁盘,无论是否持久化。在这种情况下,性能可能会受到影响,因为我们突然需要访问磁盘才能将消息发送到消费者。所以让我们进行一个测试:将大量非持久化消息发布到队列,然后将它们全部消费。
队列加载/清空 500k 消息
在这个小例子中,我们可以看到相当一致的性能:消息非常快地进入队列,然后更快地出来。
队列加载/清空 1000 万消息
但是,当我们拥有更大的队列时,我们会看到性能变化更大。我们看到,在加载队列时,最初会获得非常高的吞吐量,然后暂停一段时间,让队列的一部分分页到磁盘,然后是更一致的较低吞吐量。同样,在清空队列时,我们在从磁盘拉取消息时会看到更低的速率。
磁盘绑定队列的性能是一个复杂的话题——请查看Matthew 关于该主题的博客文章,以了解更多关于该主题的信息。
了解更多
- 网络研讨会:RabbitMQ 3.8 中有哪些新功能?
- 网络研讨会:每个使用 RabbitMQ 的开发人员都应该知道的 10 件事