跳至主内容

从镜像经典队列迁移到仲裁队列

·阅读 17 分钟
重要

关于此主题有一篇较新的博文,它使用了更现代的工具并自动化了大部分过程。

仲裁队列是经典镜像队列的优秀替代品,它在 RabbitMQ 3.8 版本中引入。您需要迁移的原因有两个互补。

首先,经典镜像队列已在 3.9 版本中弃用,于 2021 年 8 月 21 日正式发布了公告。它们将在 4.0 版本中完全移除。

而且它们更可靠、更可预测,对大多数工作负载更快,维护也更少——所以您不应该觉得自己被无缘无故地逼迫。

仲裁队列在所有方面都更好,但它们在功能上与镜像队列不是 100% 兼容的。因此,迁移看起来可能是一项艰巨的任务。

在对未来的性能改进进行一窥之后,本文概述了几种可能的迁移策略,并提供了如何处理不兼容功能的指南。此外,还有迁移您的 RabbitMQ 镜像经典队列到仲裁队列文档可帮助您完成迁移过程。

性能改进

RabbitMQ 3.10 性能改进博文中,已经详细讨论了仲裁队列的性能优势。

在下面的图中,您可以预测到尚未发布的 RabbitMQ 3.12 所能达到的新性能水平。

Quorum Queues vs Mirrored Queues Performance Showcase
仲裁队列与镜像队列性能展示

此图显示了在使用 1kB 消息的不同工作负载下的吞吐量。值越高越好,尽管在某些测试中最大吞吐量受到限制(在这些测试中,我们关注延迟和/或吞吐量是否稳定)。

颜色说明如下:

  • 橙色 - 仲裁队列
  • 绿色 - 镜像经典队列 v1(非惰性)
  • 黄色 - 镜像经典队列 v1(惰性)
  • 蓝色 - 镜像经典队列 v2

在不深入细节的情况下,我们可以看到仲裁队列在几乎所有情况下都提供了显著更高的吞吐量。例如,第一个测试是单个队列、单个发布者、单个消费者测试。仲裁队列可以维持 30000 条消息/秒的吞吐量(同样,使用 1kb 消息),同时提供高水平的数据安全并将数据复制到集群的所有 3 个节点。与此同时,经典镜像队列仅提供三分之一的吞吐量,数据安全保证却低得多。在某些测试中,我们可以看到仲裁队列(橙色线)完全平坦,这意味着它们可以承受工作负载并且仍有剩余容量(否则它们的性能会开始波动),而镜像队列提供的吞吐量较低且不稳定。

敏锐的读者可能会注意到,在第二次测试中,仲裁队列最初提供了非常高的发布者吞吐量,但很快就下降了。这正是我们目前正在处理的问题,我们希望很快能得到改善。这只是一个没有消费者的极端情况,队列很快会变得非常长(数百万条消息)。

兼容性注意事项

RabbitMQ 文档有一个专门的页面介绍仲裁队列。具体来说,该文档中有一个功能矩阵,其中列出了镜像经典队列和仲裁队列之间的所有差异。这些差异可能需要不同程度的工作才能成功迁移。其中一些更改可能很简单,而另一些则可能需要更改应用程序与 RabbitMQ 交互的方式。所有这些都已在后续内容中得到详尽记录。

并且不言而喻,迁移后的应用程序应针对仲裁队列进行彻底测试,因为在负载和边缘情况下行为可能有所不同。

本文档描述了 2 种迁移策略:

  • 第一种策略涉及创建一个新的 vhost,并通过联邦插件最大限度地减少停机时间进行迁移。如果所有不兼容的功能都已清理或移至策略,那么这也是一种理想的迁移方式——现有的代码只需更改连接参数即可同时适用于镜像队列和仲裁队列。
  • 另一种策略以牺牲正常运行时间为代价来重用同一个虚拟主机,并需要能够停止给定队列的所有消费者和发布者。

一般要求

  1. 集群中至少应有 3 个节点——使用仲裁队列且副本数量少于此没有意义。
  2. 管理插件应至少在一个节点上运行——它用于导出/导入单个主机的定义,这可以极大地简化定义清理。(rabbitmqadmin CLI 命令也在后台使用该插件)。
  3. 应启用 Shovel 插件。

查找正在使用的队列和功能

所有镜像经典队列在其有效策略定义中都包含 ha-mode。可以通过以下脚本找到应用它的策略:

#!/bin/sh
printf "%s\t%s\t%s\t%s\t%s\t%s\n" vhost policy_name pattern apply_to definition priority
for vhost in $(rabbitmqctl -q list_vhosts | tail -n +2) ; do
rabbitmqctl -q list_policies -p "$vhost" |
grep 'ha-mode'
done

但更简单的方法是列出实际在运行系统中镜像的队列。这样就无需猜测 HA 策略是否已实际应用。

#!/bin/sh
printf "%s\t%s\t%s\n" vhost queue_name mirrors
for vhost in $(rabbitmqctl -q list_vhosts | tail -n +2) ; do
rabbitmqctl -q list_queues -p "$vhost" name durable policy effective_policy_definition arguments mirror_pids type |
sed -n '/\t\[[^\t]\+\tclassic$/{s/\t\[[^\t]\+\tclassic$//; p}' |
xargs -x -r -L1 -d '\n' printf "%s\t%s\n" "$vhost"
done

请注意,上述命令使用了 effective_policy_definition 参数,该参数仅在 3.10.13/3.11.5 版本后可用。如果它不可用,可以使用新版本 RabbitMQ 的 rabbitmqctl,或者手动将策略名称与其定义进行匹配。

破坏性更改

当使用以下一个或多个功能时,无法直接迁移到仲裁队列。应用程序与代理的交互方式需要更改。

本节概述了如何查找这些功能是否在运行系统中被使用,以及需要做出哪些更改以便更容易迁移。

优先级队列

经典镜像队列实际上在后台为每个优先级创建单独的队列。对于迁移,应用程序有必要显式处理这些队列的创建,以及发布/从它们消费。

此功能可以通过在上面的队列列表中查找 x-max-priority 来检测。可以在源代码中搜索完全相同的字符串。优先级队列无法通过策略创建,因此不涉及策略更改。

溢出死信

仲裁队列不支持溢出模式 reject-publish-dlx。代码需要更新为使用发布者确认并自行处理死信。

此功能可以通过在上面的队列列表中查找 reject-publish-dlx 来检测。可以在源代码中搜索完全相同的字符串。

全局 QoS(消费者)

仲裁队列不支持全局 QoS(消费者)。需要决定如何通过其他方式实现必要的结果,例如使用较低的每个消费者 QoS,它(考虑到已知的应用程序负载模式)可以提供大致相同的结果。

要检测是否使用了此功能,可以执行以下命令并检查是否有非空输出:

rabbitmqctl list_channels pid name global_prefetch_count | sed -n '/\t0$/!p'

它将给出已启用全局 QoS 的通道 PIDs 列表,然后可以将其映射到队列名称并检查是否为镜像队列。

rabbitmqctl list_consumers queue_name channel_pid

x-cancel-on-ha-failover(消费者)

镜像队列的消费者可以在队列领导者故障转移时自动取消。这可能导致丢失有关哪些消息已发送到哪个消费者以及这些消息的重传的信息。

仲裁队列受此行为影响的程度较小——唯一仍然可能发生这种情况的是整个节点发生故障。对于其他领导者更改(例如由重新平衡引起),将不会发生重传。

当消费者被取消或通道关闭时,也会发生进行中的消息的重传。因此,应用程序无论如何都应为重传做好准备,而无需专门请求此类信息。

简单的更改

当使用仲裁队列时,这些功能不起作用。处理它们的最佳方法是完全从源代码中删除它们,或者将它们移到策略中。

惰性队列

经典队列可以选择性地以惰性模式运行,但对于仲裁队列,这是唯一的运行方式。迁移处理此问题的最佳方法是将 x-queue-mode 从源代码移至策略。

非持久化队列

非持久化队列将在节点/集群启动时被删除。具有镜像提供的额外持久化保证有点没有意义。

非持久化队列的概念在未来的版本中也将消失:短暂队列唯一的选择将是独占队列。这仅影响队列定义的持久性,消息仍可标记为瞬态。

对于此类队列,必须做出决定:此队列的内容是否足够重要以获得仲裁队列的可用性保证,或者是否最好将其降级为经典(但持久化)队列。

独占队列

即使策略说明,独占队列也不会被镜像。但尝试声明一个独占的仲裁队列将导致错误。这显然是无需迁移的情况之一,但必须注意避免使用显式的 x-queue-type: quorum 参数声明独占队列。

一次迁移一个虚拟主机中的队列

重要

关于此主题有一篇较新的博文,它使用了更现代的工具并自动化了大部分过程。

从经典镜像队列迁移到仲裁队列的过程与蓝绿集群升级类似,只是迁移可以发生在同一 RabbitMQ 集群的新虚拟主机上。联邦插件然后用于无缝地从旧虚拟主机迁移到新虚拟主机。

此迁移路径的一个重要方面是,可以为新虚拟主机指定默认队列类型。将其设置为 quorum 会使所有没有显式类型的队列(独占、非持久化或自动删除队列除外)创建为仲裁队列。

如果所有不兼容的功能都已从源代码中清理干净(并且源代码中没有显式的 x-queue-type 参数),那么使用完全相同的代码即可同时适用于带有经典镜像队列的旧虚拟主机和带有仲裁队列的新虚拟主机——只需更改连接参数中的虚拟主机。

创建目标虚拟主机

需要特别注意确保新虚拟主机以正确的默认队列类型创建。在通过管理 UI 添加新虚拟主机时,应从队列类型下拉菜单中选择它。也可以使用 CLI 界面创建它,指定默认队列类型并添加一些权限。

rabbitmqctl add_vhost NEW_VHOST --default-queue-type quorum
rabbitmqctl set_permissions -p NEW_VHOST USERNAME '.*' '.*' '.*'

创建联邦上游

重要

关于此主题有一篇较新的博文,它使用了更现代的工具并自动化了大部分过程。

应为 NEW_VHOST 创建一个新的联邦上游,URI 指向 OLD_VHOST:amqp:///OLD_VHOST。(请注意,默认 vhost URI 是 amqp:///%2f)。

可以通过管理 UI 或 CLI 创建上游:

rabbitmqctl set_parameter federation-upstream quorum-migration-upstream \
--vhost NEW_VHOST \
'{"uri":"amqp:///OLD_VHOST", "trust-user-id":true}'

当使用这种形式的 URI(带有空主机名)时,无需指定凭据,但连接仅限于单个集群。

如果出于任何原因使用了消息中的 user-id,它也可以按上述 CLI 示例进行保留。

迁移定义

重要

关于此主题有一篇较新的博文,它使用了更现代的工具并自动化了大部分过程。

将源虚拟主机的定义导出到文件。这可以在管理 UI 的“概述”页面上找到(不要忘记选择单个虚拟主机)。或使用以下 CLI 命令:

rabbitmqadmin export -V OLD_VHOST OLD_VHOST.json

在将此文件加载回 NEW_VHOST 之前,需要对该文件进行以下更改:

  1. 删除您希望在旧虚拟主机中保留为经典队列,在新虚拟主机中保留为仲裁队列的队列的 x-queue-type 声明。
  2. 需要应用于队列定义的其他更改:
    • 删除 x-max-priority 参数
    • x-overflow 参数设置为 reject-publish-dlx 时进行更改
    • 删除 x-queue-mode 参数
    • durable 属性更改为 true
  3. 更改策略中的以下键:
    • 删除所有以 ha- 开头的项:ha-modeha-paramsha-sync-modeha-sync-batch-sizeha-promote-on-shutdownha-promote-on-failure
    • 删除 queue-mode
    • overflow 设置为 reject-publish-dlx 时进行更改
  4. 经过上一步处理后变为空的策略应被删除。
  5. 应将旧 vhost 的联邦添加到任何剩余策略中,指向之前创建的联邦上游:"federation-upstream-set":"quorum-migration-upstream"
  6. 如果没有通配符策略(应用于模式为 .* 的队列),则需要创建该策略并也指向联邦上游。这确保了旧 vhost 中的每个队列都将被联合。
  7. 在迁移期间,应删除应用于交换机的联邦规则的策略,以避免重复消息。

现在可以通过 UI 或 CLI 工具将修改后的模式加载到新虚拟主机中。

# Import definitions for a single virtual host using rabbitmqadmin.
# See https://rabbitmq.cn/docs/definitions to learn more.
rabbitmqadmin import -V NEW_VHOST NEW_VHOST.json

将消费者指向新虚拟主机

重要

关于此主题有一篇较新的博文,它使用了更现代的工具并自动化了大部分过程。

此时,应只需更新连接参数即可将消费者指向新虚拟主机。

将生产者指向新虚拟主机

重要

关于此主题有一篇较新的博文,它使用了更现代的工具并自动化了大部分过程。

现在也可以将生产者指向新虚拟主机。

停止消费者的同时,也应在旧 vhost 中禁用联邦交换机,并在新 vhost 中启用它们。

在系统负载足够高的情况下,来自旧虚拟主机的消息将不会被获取。如果消息顺序很重要,那么应该分步进行:停止发布者,将剩余消息推送到新虚拟主机,然后在新虚拟主机上启动消费者。

将剩余消息推送到新虚拟主机

对于旧主机中的每个非空队列,都需要配置一个 Shovel。

rabbitmqctl set_parameter shovel migrate-QUEUE_TO_MIGRATE \
'{"src-protocol": "amqp091", "src-uri": "amqp:///OLD_VHOST", "src-queue": "QUEUE_TO_MIGRATE",
"dest-protocol": "amqp091", "dest-uri": "amqp:///NEW_VHOST", "dest-queue": "QUEUE_TO_MIGRATE"}'

队列被清空后,Shovel 可以被删除。

rabbitmqctl clear_parameter shovel migrate-QUEUE_TO_MIGRATE

确保未来的队列声明成功

重要

关于此主题有一篇较新的博文,它使用了更现代的工具并自动化了大部分过程。

许多应用程序会在使用它们之前在多个地方声明队列。当迁移到仲裁队列时,这会造成一个通道问题:如果客户端在未明确提供队列类型的情况下声明队列,在迁移定义步骤之后,所有未来的声明尝试都会遇到 PRECONDITION_FAILURE 通道错误,因为现有队列被重新声明。

为了避免这种情况,有三种选择:

  1. x-queue-type 声明加回到所有使用仲裁队列的客户端。
  2. 使用 default_queue_type 在节点范围内设置默认队列类型,这是一个在 RabbitMQ 3.13.3 及更高版本中可用的 rabbitmq.conf 设置。(链接)
  3. 设置 quorum_queue.property_equivalence.relaxed_checks_on_redeclaration = true,这是一个自 RabbitMQ 3.11.16 起可用的 rabbitmq.conf 设置。( 链接)

第三种选项 quorum_queue.property_equivalence.relaxed_checks_on_redeclaration 设置为 true,可以在迁移过程中的任何时间采用。

原地迁移

在本次迁移过程中,我们以牺牲正常运行时间为代价,以便在现有虚拟主机和集群中执行迁移。

对于正在迁移的每个队列(或一组队列),应能够在迁移期间停止其所有消费者和发布者。

准备发布者和消费者

所有不兼容的功能都应被清理。此外,在声明队列的每个地方,最好能配置 x-queue-type 参数而不更改应用程序代码。

迁移步骤

重要

关于此主题有一篇较新的博文,它使用了更现代的工具并自动化了大部分过程。

  1. 首先,需要停止消费者和发布者。
  2. 消息应被推送到一个新的临时队列。
  3. 旧队列应被删除。
  4. 应创建一个新的仲裁队列,其名称与原始队列相同。
  5. 临时队列的内容现在应被推送到新的仲裁队列。
  6. 消费者现在可以重新配置为使用 x-queue-typequorum 并启动。

结论

希望这篇博文表明,通过适当的准备,迁移可以是有益且相对简单的。

迁移有很多好处。但也要记住,经典镜像队列已被弃用一年多,并且将在即将发布的版本中完全移除。所以即使您现在不打算进行迁移,提前做好这些准备也是个好主意。

我们已努力为您提供全面的指南。也许是时候采取行动了?

© . This site is unofficial and not affiliated with VMware.