同步的终结:3.3 版本中的性能改进
好吧,我们昨天已经把坏消息抛在了脑后,所以今天让我们来谈谈(一些)好消息:某些类型的发布和消费现在快了很多,尤其是在集群中。
RabbitMQ 的各个内部组件通过相互传递消息进行通信(在节点内部和跨集群);这就是 Erlang 应用程序的工作方式。RabbitMQ 的一个始终如一的的设计目标是,在 AMQP 中异步的操作(即发送和接收消息以及确认)应该在服务器内部也是异步的。这样做是有充分理由的:每当你执行一个同步操作时,你都会受到等待回复的延迟的限制,因此异步性是实现更快消息传递的途径。
不幸的是,虽然这始终是一个目标,但我们并不总是能达到它。特别是在两种情况下,AMQP 中的异步消息传递在服务器内部变成了同步:强制发布和使用通过basic.qos
设置的预取限制进行消息消费。这些问题在 3.3.0 中得到了修复。
简单回顾一下,强制发布意味着**告诉发布者其消息是否最终没有路由到任何队列**,而使用预取限制进行消费则意味着**确保你只向消费者发送最大数量的未确认的挂起消息**。
所以让我们来看一些数据……
强制发布
3.2.4 | 3.3.0 | |
---|---|---|
强制发布 | 5.0kHz 平衡 | 12.9kHz 平衡 |
此测试在一个单机上的双节点集群中进行,发布者连接到一个节点,消费者连接到另一个节点,队列位于与消费者相同的节点上。消息很小且非持久化,既不使用确认也不使用确认。机器是戴尔 Precision 工作站,但重点是查看这里的相对性能变化。
希望您能看到同步对性能的损害有多严重。并且请记住,同步消息传递带来的性能损失与网络延迟成正比——这两个节点位于同一台机器上,因此真实的集群会下降得更厉害。
还要注意,在这两种情况下,发送和接收速率都是相同的;消息没有在队列中积压。
使用预取限制进行消费
我们预计,较高的预取限制将提供与没有预取限制几乎相同的性能,并且随着我们降低限制,性能也会降低,因为在某些时候,队列必须等到消费者确认一条消息后才能发送另一条消息。
3.2.4 | 3.3.0 | |
---|---|---|
无限制 | 发送 15.0kHz / 接收 11.0kHz | 15.8kHz 平衡 |
prefetch_limit=1000 | 发送 6.2kHz / 接收 3.6kHz | 15.8kHz 平衡 |
prefetch_limit=100 | 发送 6.2kHz / 接收 3.6kHz | 13.5kHz 平衡 |
prefetch_limit=10 | 发送 6.2kHz / 接收 3.6kHz | 发送 14.0kHz / 接收 7.0kHz |
prefetch_limit=1 | 发送 18.0kHz / 接收 0.9kHz | 发送 18.0kHz / 接收 0.9kHz |
此测试与上述测试具有相同的特征,只是队列位于与发布者相同的节点上,并且在消费时使用了确认。
表中的数字显示了一些有趣的现象
- 即使在关闭预取限制的情况下,3.3.0 也略快,并且防止了消息积压。这是由于一项新功能,我将在以后的博客文章中讨论。
- 在 3.3.0 中,足够高的预取限制(使得队列永远不必等待消费者)不会产生性能成本,而在 3.2.4 中,任何预取限制都会损害性能。
- 在 3.2.4 中,10、100 和 1000 之间的所有预取限制都具有完全相同的(糟糕的)性能——这是因为限制因素实际上是消费通道和队列之间的同步通信。
- 最后,当我们达到 1 的预取限制时,3.2.4 和 3.3.0 的性能都同样糟糕——这是因为限制因素现在变成了我们等待消费者一次发送一条消息的确认所需的时间。
因此,通过这些更改,RabbitMQ 的消息传递内部现在在所有情况下都为异步,从而带来了巨大的性能提升。值得指出的是,为了实现这一点,basic.qos
的语义必须略微改变,但这对于如此巨大的改进来说似乎是一个很小的代价。