RabbitMQ 3.13:经典队列变更
我们已经在之前的博客文章中宣布了 3.13 的两个主要新功能
这篇文章重点介绍此版本中经典队列的更改
- 经典队列存储格式版本 1 已弃用
- 经典队列消息存储的新实现
经典队列存储入门
在深入了解这些更改之前,有必要简要解释一下经典队列如何存储消息。对于每条消息,我们需要存储消息有效负载和一些关于消息的元数据(例如,此消息是否已传递给消费者)。将消息数据(不透明的二进制 blob,可能大小可观)与元数据(小的键值对映射)分开是有意义的。然而,对于小消息,执行两次单独的写入,一次用于元数据,另一次用于消息内容,是浪费的。因此,经典队列处理小消息的方式与处理大消息的方式不同。
从历史上看,在我们现在称为经典队列版本 1 的版本中,此过程称为将消息嵌入索引,属性 queue_index_embed_msgs_below
控制着什么构成足够小的消息以进行嵌入(默认值为 4kB)。超过此阈值的消息将单独存储在消息存储中 - 一个单独的键值结构,具有不同的磁盘表示形式。对于存储在存储中的消息,索引包含元数据和消息存储 ID,这允许在需要时检索有效负载。每个虚拟主机有一个消息存储,而每个队列都有一个单独的索引。
在 3.10 中引入的经典队列存储版本 2 在逻辑上非常相似:仍然有相同的每个虚拟主机消息存储和单独的每个队列消息存储,用于元数据和小消息。但是,我们每个队列存储的内容的结构完全不同,因此我们不再仅仅称其为索引 - 小消息不会嵌入在索引中,而是存储在每个队列消息存储中的单独文件中。
每个虚拟主机消息存储仍然存在用于较大的消息,但 3.13 版本显着改变了其行为。
为了向后兼容,queue_index_embed_msgs_below
仍然控制着消息是否足够大到可以存储在每个虚拟主机消息存储中,默认值仍然是 4kB。
经典队列版本 2 (CQv2)
几年前,我们开始了重新实现经典队列以获得更好性能的旅程。自最初的实现以来,许多事情都发生了变化,现在最初的实现已经有近 20 年的历史了!以下是此旅程的步骤概述
- 自 3.10 以来,
queue-version=2
的队列使用新的索引存储格式(我们以不同的方式存储每个队列的数据) - 自 3.12 以来,经典队列(v1 和 v2)永远不会在内存中存储超过一小部分消息
- 自 3.12 以来,低于
queue_index_embed_msgs_below
(默认为 4kb)的消息处理效率更高
通过 3.13,我们正接近此旅程的尾声
- 从 3.13 开始,高于
queue_index_embed_msgs_below
的消息将以更有效的方式处理 - 从 3.13 开始,经典队列 v1 已弃用
在 RabbitMQ 4.0 中,我们将删除经典队列的镜像功能。正如我们之前多次说过的那样,如果您需要高可用的复制队列,您应该使用自 3.8 版本以来可用的仲裁队列。删除镜像功能将能够进一步优化实现。
此外,在 4.0 中,我们很可能会删除队列索引的 v1 实现(这可能会根据您的反馈而延迟!)。当您将来升级到 4.0 时,所有仍在使用 v1 的经典队列都需要在启动期间转换为 v2。如果消息很多和/或队列很多,这可能需要很长时间。因此,最好有意识地完成转换过程。
如果我没有为 CQv2 做好准备怎么办?
在索引实现的版本 1 被删除之前,您仍然可以使用它。
消息存储实现没有这样的选择 - 3.13 包含显着的改进,尤其是在与 v2 索引结合使用时。但是,当与 v1 结合使用时,也可能存在轻微的回归。建议用户彻底测试他们的应用程序,并报告 v2 索引比 v1 更差的情况。
CQv1 -> CQv2 转换
由于 v1 和 v2 使用不同的文件格式,因此如果队列从 v1 更改为 v2(或反之亦然 - 支持降级),则需要进行转换。如果您有现有的经典队列 v1 并应用了 x-queue-version=2
的策略,则此队列将在转换期间不可用 - 队列需要一些时间将文件重写为新格式。此类转换不应超过几秒钟 - 如果您发现它花费的时间更长,请报告此情况。
由于队列版本可以通过策略更改,因此也可以逐步从 v1 迁移到 v2。您可以声明一个仅匹配队列子集的策略,一旦它们被转换,您可以扩展正则表达式以匹配更多队列,或者声明另一个匹配不同队列子集的策略。即使策略匹配很多队列,迁移也严格来说是每个队列的操作 - 任何完成转换的队列在转换完成后立即可以为客户端应用程序提供服务,即使其他队列仍在重写它们的文件。
您可以在 3.12(甚至 3.10 或 3.11)上完成此转换。如果您这样做,那么 4.0 中 v1 的删除实际上不会影响您,因为您的所有队列都将已经是 v2。
性能比较
让我们看看比较 RabbitMQ 3.12.11 与 3.13.0-rc.4 的结果。有关基准测试设置以及我们运行这些测试的方式的详细信息,请参阅之前的博客文章,或查看存储库,我们在其中保留了环境配置和包含工作负载的脚本。
所有测试均使用 100B 和 5kB 消息执行。
发布和消费
在此测试中,我们有一个发布者和一个消费者,只是尝试通过单个队列尽可能快地传递消息。
如您所见,与 CQv1 相比,经典队列 v2 提供了显着更好的性能,并且 3.13 提高了这两个版本的性能。对于仍然使用 CQv1 的用户,在 3.13 上迁移到 CQv2 可能会使小消息的吞吐量几乎翻倍!


仅发布
在此测试中,我们以全速发布到队列,有 2 个发布者,但根本不消费消息。队列从空增长到 500 万条消息。
对于 100B 消息,CQv2 将 CQv1 远远甩在后面,吞吐量比 3.12 高出 250% 以上。

5kB 测试更加微妙。3.13 与 CQv2 以很大优势获胜,即使在 3.12 中,CQv2 的优势也很明显。然而,新消息存储与旧索引的组合性能不一致 - 它在大多数时候具有良好的吞吐量,但有明显的减速(延迟峰值)。这是不幸的,但我们决定保持这种方式,考虑到触发此行为所需的因素数量以及用户无论如何都应该迁移到 CQv2 的事实。我们仅在此测试中看到了这种行为,因此需要满足以下条件:一个 3.13 节点运行一个 CQv1 队列,其中消息大于 4kb(或 queue_index_embed_msgs_below
的任何值),发布者明显快于消费者(或根本没有消费者)以及消息的高吞吐量。如果您有这样的工作负载,迁移到 CQv2 不仅应防止此回归,而且还会为您带来比 CQv1 永远可以实现的性能显着更好的性能。

仅消费
在此测试中,我们消费了先前测试中的大量积压消息。有 500 万条消息要消费(希望您的队列短得多!)。
对于 100B 消息,您可以看到 CQv2 在早期提供了约 30% 更高的消费率。随着时间的推移,随着队列变短,CQv1 变得更快,但 CQv2 环境仍然在 CQv1 之前清空队列(当消费率降至零时,这意味着队列为空)

对于由每个虚拟主机消息存储处理的 5kB 消息,您可以看到 3.13 更改的主要好处。由于消息存储实现由 v1 和 v2 共享,因此在此测试中,即使是带有 CQv2 的 3.12,两个 3.13 环境也明显领先于 3.12。我们可以看到 3.13 中的吞吐量提高了约 50%

多个队列
在此测试中,我们没有将单个队列推到极限,而是有 5 个并发消息流:5 个发布者,每个发布者发布到不同的队列,以及 5 个消费者,每个队列一个消费者。每个发布者每秒发送 10000 条消息,因此总预期吞吐量为 50000 条消息。对于 100B 消息,所有环境都达到了预期的吞吐量,而对于 5kB 消息,所有环境都在 27000 条消息/秒左右波动。这里更有趣的部分是端到端延迟 - 从消息发送到消息被消费所花费的时间。
对于 100B 消息,我们可以看到 CQv2 环境可以更快地传递消息。对于从 3.12 上的 CQv1 迁移到 3.13 上的 CQv2 的用户来说,平均延迟减少了 75%,并且内存使用量减少了 50%。

对于 5kB 消息,结果更接近,实际上,3.12 赢得了这项特定测试(我们将来可能会研究)。但是,3.13 仍然可以实现类似的结果,同时使用更少 100MB 的内存

发布者确认延迟
最后,让我们看一下一个非常不同的测试。我们不是用消息淹没队列,而是一次只发布一条消息,等待发布者确认,然后发布下一条消息(消费者存在,但在这里并不真正相关,因为它很容易消费传入的消息)。
对于 100B 消息,我们可以再次看到 CQv2 有多快,与 3.12 上的 CQv1 相比,速度提高了 200% 以上

对于 5kB 消息,3.13 中新的每个虚拟主机消息存储实现的优势显而易见,改进超过 350%

您可能会注意到,5kB 消息的吞吐量实际上比 100B 消息的吞吐量略高。这违反直觉,但实际上并没有那么奇怪。5kB 对于通过网络发送的数据来说仍然是一个很小的量,而消息存储旨在更好地处理此类消息。与所有其他内容一样,这种差异可能会在未来随着进一步的优化以及可能更改我们将考虑的 queue_index_embed_msgs_below
的默认值而改变。
注意事项
索引的版本 2 和新的消息存储实现都应该为大多数用户提供显着的好处。但是,这些实现已经专注于非镜像用例,并且新的消息存储实现是考虑到索引的 v2 而设计的。虽然它向后兼容并且可以与经典队列 v1 一起使用,但在某些情况下,将索引的 v1 与新的消息存储一起使用可能会提供比过去更差的性能或不同的性能配置文件,如仅发布测试中所示。因此,强烈建议
- 使用 3.13 测试和基准测试您的应用程序
- 比较 v1 和 v2 的性能
- 如果 v1 在没有镜像的情况下表现更好,请报告,以便我们查看
在 v1 提供更好性能的情况下,保留在 v1 上可能是一种解决方法。但是,旧的实现将在未来被删除,因此您不能长期依赖该解决方法。请报告此类情况,以便您将来可以升级到 v2。
总结
重新设计和替换广泛使用的软件的核心组件是一项非常艰巨的任务。特别感谢 Loïc Hoguin 参与此项目,梳理代码,有时代码可以追溯到 RabbitMQ 的第一个版本,那时世界还不知道 iPhone。与往常一样,我们欢迎测试和反馈,我们希望升级将为您带来与上述类似的收益。