RabbitMQ 3.12 性能改进
RabbitMQ 3.12 将很快发布,其中包含许多新功能和改进。这篇博文重点介绍了与性能相关的差异。最重要的变化是经典队列的 lazy
模式现在是标准行为(更多内容见下文)。新的实现应该更加节省内存,同时提供比早期版本中 lazy
或 non-lazy
实现更高的吞吐量和更低的延迟。
为了获得更好的性能,我们强烈建议切换到经典队列版本 2 (CQv2)。
概述
让我们快速浏览一下 RabbitMQ 3.12 中最重要的与性能相关的改进。
经典队列:对延迟模式的更改
从 3.12 开始,x-queue-mode=lazy
参数被忽略。所有经典队列现在将与以前延迟队列的行为类似。也就是说,消息倾向于写入磁盘,只有少数消息保存在内存中。内存中的消息数量取决于消费速率。此更改会影响所有经典队列用户。根据我们的测试,对于绝大多数用户来说,新的实现应该会带来性能的显著提升,并降低内存使用量。继续阅读一些基准测试结果,但也要确保在升级之前使用 PerfTest 测试您的系统。
经典队列:极大地改进的经典队列 v2 (CQv2)
本段已更新以反映路线图的变更。经典队列版本 2 将成为 RabbitMQ 4.0 中的默认选项,也是唯一选项(之前我们计划在 3.13 中将其设为默认选项)。
从 RabbitMQ 3.10 开始,我们已经有了 两种经典队列的实现:原始的 (CQv1) 和新的 (CQv2)。它们之间的差异主要在于磁盘存储。
大多数用户仍在使用 CQv1,但从 3.12 开始,我们强烈建议切换到 CQv2,或者至少对其进行评估。版本 2 将成为 RabbitMQ 4.0 中唯一可用的实现。
迁移过程很简单:在声明队列时添加一个新的策略键或一个可选的队列参数 x-queue-version=2
。要全局切换到 CQv2,请在配置文件中将 classic_queue.default_version
设置为 2
classic_queue.default_version = 2
可以通过将版本设置回 1 来回退。每次值更改时,RabbitMQ 都会转换队列的磁盘表示形式。对于大多数为空的队列,此过程是瞬时的。对于合理的后备日志,可能需要几秒钟。
在许多用例中,切换到 CQv2 将导致吞吐量提高 20-40%,同时降低内存使用量。那些仍然使用经典队列镜像 (您不应该使用它,它很快就会被移除!) 的用户需要更彻底地测试他们的系统,因为在某些情况下,版本 1 在镜像经典队列中效果更好,但在许多情况下,版本 2 效果更好,即使没有镜像相关的代码优化。
新的 MQTT 实现:每个连接显着节省内存,每个节点支持数百万个连接
对 MQTT 插件 进行了全面重新设计,它提供了更低的内存使用量、更低的延迟,并且可以处理比以前更多的连接。
我们发布了 一篇关于 MQTT 相关改进的单独博文。
对仲裁队列的重大改进
使仲裁队列更有效的工作仍在继续。RabbitMQ 3.12 带来了许多改进,因此所有仲裁队列用户都应该看到更好的性能。最大的变化将体现在具有长仲裁队列的环境中。之前,随着队列变长,其吞吐量会下降。这应该不再是一个问题。
节点应该更快地停止和启动
具有许多经典队列(数万个或更多)的 RabbitMQ 节点应该更快地停止和启动。这意味着在升级和其他维护操作期间,节点停机时间更短。
基准测试设置
下面显示的所有数据比较了 3.11.7 和 3.12-rc.2。一些(大部分较小的)优化已被移植到更新的 3.11 补丁版本,因此此比较没有使用最新的 3.11 补丁版本。
基准测试
在撰写本文时,RabbitMQ 团队的标准性能测试套件包含 14 个测试。每个测试运行 5 分钟。单独的环境以相同的时间使用不同的消息大小进行相同的测试,这样便于查看消息大小的影响,或者比较不同队列类型或版本在相同负载下的表现。
以下是测试列表,按其在 Grafana 仪表板中出现的顺序排列
- 一个发布者尽可能快地发布消息,而一个消费者尽可能快地消费消息
- 两个发布者,没有消费者(队列变长时的性能)
- 一个消费者从上一个测试中的长队列中消费消息(消费者性能不受发布者的影响)
- 五个队列,每个队列都有 1 个发布者以 10k 消息/秒的速度发布消息,1 个消费者(预计总吞吐量为 50k/秒,我们关注延迟)
- 扇出到 10 个队列 - 1 个发布者和 10 个消费者,一个扇出交换机
- 一个发布者,一个消费者,但只有一个未确认的消息(发布者在发送下一条消息之前等待上一个消息的确认)
- 扇入:7000 个发布者以每秒 1 条消息的速度发布消息,到一个队列
- 1000 个发布者以每秒 10 条消息的速度发布消息,每个发布者发布到不同的队列;每个队列也有一个消费者(预计总吞吐量:10k/秒)
- 一个发布者没有消费者会创建消息后备日志,然后 10 个消费者加入来清空队列
- 类似于上一个测试,但 50 个消费者加入,但设置了单一活跃消费者(因此只有一个开始清空队列)
- 类似于第一个测试,但在消费者端使用 1000 的多重确认
- 发布具有 TTL 的消息,这些消息会很快过期(它们不会被消费)
- 发布消息以供稍后否定确认(这只是下一个测试的设置)
- 对上一个测试中的消息进行否定确认
最后几个测试不太有趣。它们被引入是为了查找某些特定区域的问题。
环境
这些测试是在以下环境中进行的
- 一个使用 e2-standard-16 节点的 GKE 集群
- 使用 我们的 Kubernetes 运算符 部署的 RabbitMQ 集群,具有以下资源和配置
apiVersion: rabbitmq.com/v1beta1
kind: RabbitmqCluster
metadata:
name: ...
spec:
replicas: 1 # or 3 for mirrored and quorum queues
image: rabbitmq:3.11.7-management # or rabbitmq:3.12.0-rc.2-management
resources:
requests:
cpu: 14
memory: 12Gi
limits:
cpu: 14
memory: 12Gi
persistence:
storageClassName: premium-rwo
storage: "150Gi"
rabbitmq:
advancedConfig: |
[
{rabbit, [
{credit_flow_default_credit,{0,0}}
]}
].
关于环境的一些说明
- 对于许多测试(甚至生产工作负载),这些资源设置过于充足。这只是我们在性能测试中使用的配置,并非建议。
- 您应该能够使用更好的硬件获得更高的值。
e2-standard-16
远非市面上最好的硬件。 - 信用流已禁用,否则单个快速发布者将被节流(以防止过载并为其他发布者提供公平的机会);流量控制在具有许多用户/连接的系统中很重要,但通常不是我们在基准测试时想要的东西。
测试结果
经典队列的新延迟类默认行为和经典队列 v2
RabbitMQ 的初始版本发布于 2007 年。当时,磁盘访问速度与任何其他操作相比都非常慢。然而,随着存储技术的不断发展,多年来,避免将数据写入磁盘的必要性越来越小。在 2015 年的版本 3.6.0 中,延迟队列 被添加为一个选项。延迟队列将所有消息存储在磁盘上以节省内存,这对于可能变长的队列尤其重要。如今,将消息写入磁盘是一个相当便宜的操作(除非您执行 fsync
以保证高数据安全性,就像仲裁队列一样)。通过将消息存储在磁盘上,我们可以使用更少的内存。这意味着更低的成本、更少的内存警报以及更少因集群中的内存峰值突然出现而造成的头痛。因此,我们已将此设为经典队列的唯一选项。
3.11 非延迟 vs 3.12
让我们首先看一下,对于目前使用非延迟经典队列版本 1 (CQv1) 的用户来说,升级后我们预计会发生什么。屏幕截图来自 12 字节消息大小测试。
如您所见,3.12 在所有测试中表现更好:吞吐量更高,延迟更低,可变性更小(速率波动更少)。同时,3.12 的内存使用量也低得多(类似于延迟队列)。在最后一个面板中,您可以看到 3.11 的内存峰值会在队列变长时出现,而 3.12 只在涉及多个连接的测试中超过 1GB 内存使用量(是连接使用内存,而不是队列)。
3.12 CQv1 与 CQv2
上面,我们看到了大多数用户在升级到 RabbitMQ 3.12 后应该获得的一些好处,但我们才刚刚开始!让我们将经典队列版本 2 添加到比较中。
使用 CQv2,我们观察到更高的吞吐量和更低的延迟。特别是在队列变长时的第二个测试中,版本 2 的延迟不会超过 50 毫秒,而 3.11 会飙升至几秒钟。
请不要犹豫,尝试一下经典队列版本 2。您只需设置 x-queue-version=2
策略密钥即可。要全局切换到 CQv2,请在配置文件中将 classic_queue.default_version
设置为 2
。
classic_queue.default_version = 2
版本 2 将从 RabbitMQ 3.13 开始成为默认版本。
经典镜像队列
如上所述,经典队列版本 1 包含一些专门针对镜像队列行为进行优化的内容。当我们准备移除镜像功能时,版本 2 不再执行任何特殊的镜像技巧。因此,结果喜忧参半,但版本 2 非常高效,即使没有特殊考虑,它也能够在大多数情况下胜过版本 1,即使是镜像情况下也是如此。
您可以查看版本 2 是否对您有帮助,但更重要的是,请尽快开始迁移到仲裁队列和流。
仲裁队列
仲裁队列多年来一直提供比队列镜像更好的性能和数据安全,并且它们只会变得更好。
3.12 中最大的改进是仲裁队列如何处理长时间积压。
发布到长时间仲裁队列
在 RabbitMQ 3.12 之前,如果仲裁队列有长时间积压,发布延迟可能会大幅增加,从而降低吞吐量。从 3.12 开始,队列的长度对于延迟和吞吐量来说不再重要。
您可以在第二个测试中看到这一点:虽然两个版本都以大约 25k 消息/秒的速度开始,但 3.11 很快就降至 10k/秒左右。与此同时,3.12 仍保持在 20k/秒以上。
3.12 的性能不像我们希望的那样平滑,但已经好多了,并且可以进一步改进。同样重要的是要记住,在这些测试中,我们实际上是超负荷了队列:消息以尽可能快的速度不断流入,因此任何垃圾回收或周期性操作(例如 Raft 预写日志回滚)都将作为延迟峰值被观察到。
消费吞吐量
从仲裁队列消费消息的速度也比以前快得多。特别是,从非常长的队列消费消息的速度在我们的测试中可以快 10 倍。队列仍然应该保持相对较短(如果您需要存储大量消息,可以使用流),但有了这些改进,仲裁队列应该能够很好地处理各种消息积压。
请看最后一个面板上的第三个测试(每秒消费的消息数量)。3.12 从 15k 消息/秒开始,随着队列变短,速度越来越快。与此同时,3.11 几乎无法超过每秒 1000 条消息。在这个测试中,我们正在消费一个积压了 500 万条消息的队列,所以您希望永远不要看到仲裁队列像这样挣扎,但好消息是:即使在这种情况下,仲裁队列现在也应该表现得更好,并且更可预测。
更可能的情况是消费者在一段时间内不可用,需要赶上来。让我们关注这些测试。
在每个测试的第一阶段,消费者处于关闭状态,并且会创建消息积压。然后消费者开始。在第一个测试中,有 10 个消费者,在第二个测试中,有 50 个消费者,但只有一个处于活动状态(作为单个活动消费者)。在这两种情况下,3.12 都提供了显著更高的消费率,并且队列更快地清空。在 3.11 的情况下,我们可以看到它随着队列积压变短而逐渐变得更快。
更快的节点重启
这应该不会影响大多数用户,但对于拥有许多经典队列的用户来说,应该是一个非常好的消息(例如,拥有许多订阅的 MQTT 用户)。在我们的测试中,我们启动了一个节点,导入了 100,000 个经典队列版本 2,然后重启了节点。3.12 在 3 分钟内启动并运行,而 3.11 需要 15 分钟才能再次开始为客户端提供服务。3.11 在启动时遇到内存警报,这使得引导过程特别缓慢。有一个客户端在运行,只是为了查看它何时失去连接并可以重新建立连接。
没有更多具有许多空闲队列的周期性资源使用峰值
您可能已经注意到上面的屏幕截图,3.11 不仅重启花费的时间更长,而且发布率也有峰值,即使这是一个非常轻量级的负载(每秒只有 100 条消息)。峰值是由一个内部进程引起的,该进程检查队列的健康状况,以防止发出过时的队列指标。在 3.12 之前,它会查询每个队列的状态,以确定队列是否健康。但是,空闲的经典队列会休眠(它们的 Erlang 进程已停止,并且其内存已压缩),需要唤醒它们才能回复它们是健康的。从 3.12 开始,休眠的队列在没有唤醒它们的情况下被认为是健康的,因此即使在拥有许多队列的节点上,CPU 和内存使用量也应该更低,并且更一致。
结论
RabbitMQ 3.12 应该会提高几乎所有用户的性能,而且往往会显著提高。和以往一样,我们衷心建议您在升级之前测试候选版本和新版本。我们也始终乐于了解人们如何在GitHub 讨论和我们的社区 Discord 服务器中使用 RabbitMQ。
如果您能分享有关工作负载的信息,理想情况下,作为 perf-test 命令,这将有助于我们使 RabbitMQ 更加适合您。