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