2025年从经典镜像队列迁移到仲裁队列
RabbitMQ 4 已经发布一段时间了,我们已经介绍了一些它相比前代 RabbitMQ 3.13 的优点。例如,改进的性能、原生 AMQP 1.0,以及新的仲裁队列功能,这使得它与经典队列的功能趋于一致。
距离我们上次撰写关于如何从经典镜像队列 (CMQ) 迁移到仲裁队列 (QQ) 的文章已经过去一段时间了。如果您还不知道,CMQ 已在 RabbitMQ 3.9 中被弃用,并已在 RabbitMQ 4.0 中移除。今天,我们将更详细地介绍仲裁队列,将其与 CMQ 进行对比,并提供迁移的高级策略,以彻底放弃 CMQ,转而使用 QQ,或者采用 QQ 与经典队列 (CQ) 相结合的方案。请注意,CQ 仍然得到良好支持,被弃用并移除的只是其镜像功能。
为什么要选择仲裁队列?
镜像经典队列(又称 Classic Mirrored Queues,简称 CMQ)的引入是为了给经典队列 (CQ) 增加集群内数据复制功能。CQ 最初是在 2006 年 RabbitMQ 开发的第一年设计的,当时是一种非复制队列类型。
后来在 2009 年左右,镜像功能增加了将数据复制到其他节点的能力。这种“镜像”数据的算法是自研的,对网络分区不太具备弹性。更糟糕的是,在某些情况下,CMQ 在故障场景下的行为是不可预测的,且通常很难推断。甚至在某些情况下,CMQ 可能会丢失消息。
为了解决所有这些问题,并为 RabbitMQ 提供一种可靠、安全且可复制的队列类型,仲裁队列 (Quorum Queues) 被设计并引入到 RabbitMQ 中。
仲裁队列是一种从底层构建的可复制队列类型,基于 Raft 共识算法。仲裁队列在设计时将数据安全放在首位。所有 QQ 都有一个领导者(leader)和一些跟随者(followers);从逻辑上讲,领导者和跟随者分布在各个 RabbitMQ 节点上。当 QQ 领导者收到消息时,它会将该操作记录在预写式日志 (WAL) 中,然后将消息存储在本地 Raft 日志的磁盘上,并并行向跟随者发出复制命令,等待大多数节点的确认后,才会向客户端发送确认。
与 CMQ 的一个关键区别在于,QQ 的复制部分是并行执行的,而 CMQ 使用的是链式复制算法。两者的另一个重要区别是:仲裁队列通过了更严格的 Jepsen 测试,而 CMQ 连要求较低的原始版本测试都无法通过。
当领导者发生故障时,最新的跟随者会启动投票过程,并选出新的 QQ 领导者。产生的领导者将恢复队列操作。您可以在 GitHub 上了解有关 Raft 共识算法 的更多详细信息。
凭借这些特性,QQ 解决了 CMQ 的主要问题:数据安全性和故障场景的可预测性。最重要的是,仲裁队列提供了近乎完全的功能对等,并且在许多工作负载下具有更好的吞吐量。
然而,对于消息传递系统中的某些用例,QQ 并不适用。例如:RPC(请求-回复)通信中使用的临时队列。
对于生命周期极短的瞬态数据,使用仲裁队列没有意义,因为这种用例中数据安全并非优先事项。例如,如果您的应用程序使用“即发即弃”(fire-and-forget)方式发布消息,和/或您的消费者应用程序不使用手动确认。对于这些用例,**不带镜像**的经典队列是极佳的选择。
从 CMQ 迁移到 QQ
鉴于两种队列类型(包括在存储层面)的本质不同,无法将现有的经典队列“就地”转换为仲裁队列。
但是,可以使用蓝绿部署进行迁移。这是一种将应用程序从现有的带有镜像经典队列的集群(称为“蓝色”集群)迁移到新集群(“绿色”集群)的策略,绿色集群将为蓝色集群中所有已镜像的经典队列使用仲裁队列。
RabbitMQ 提供了辅助迁移的工具。`rabbitmqadmin` v2 是一款由 Broadcom RabbitMQ 核心团队构建的、基于 HTTP API 的 CLI 工具,它支持许多简化迁移的命令。
在深入细节之前,让我们看看如何将现有的使用镜像经典队列的 RabbitMQ 3.13.x 集群迁移到将为所有已复制队列使用仲裁队列的新 4.1.x 集群。
高级迁移计划
蓝绿升级策略有一些在升级前必须满足的要求
- 必须启用 RabbitMQ 队列联邦 (Queue Federation)
- 蓝色(原始)集群必须可从绿色(新)集群访问,以便队列联邦正常工作
- 绿色(新)RabbitMQ 版本必须运行
4.x版本,理想情况下是可用的最新版本 - 绿色(新)集群中必须启用所有稳定的 特性标志 (feature flags)
满足前提条件后,迁移计划包含以下步骤
- 使用
rabbitmqadminv2 导出蓝色集群的定义,并应用一些转换,确保定义中不包含新集群不支持的任何键(即 CMQ 镜像策略) - 将定义导入绿色集群
- 在两个集群之间配置队列联邦
- 将消费者迁移到绿色集群
- 将生产者迁移到绿色集群
- 监控绿色集群的状态
- 关闭原始集群
- 从绿色集群中移除若干临时迁移策略
现在,让我们深入细节。
应用程序注意事项
仲裁队列支持持久化镜像经典队列的所有特性。这意味着大多数应用程序无需任何更改即可迁移到新集群。对于明确指定队列类型的一小部分应用程序,可以使用一个特殊的 rabbitmq.conf 设置来放宽 RabbitMQ 节点在客户端尝试声明队列时执行的关键队列属性对等检查。
功能对比矩阵涵盖了仲裁队列与经典队列的区别。
现在是重新考虑某些应用是否需要使用可复制队列的好时机。例如,一个创建并绑定临时队列到 fanout 交换机、处理一段时间信息并在断开连接后删除其队列的应用程序,不会从可复制队列类型(仲裁队列)提供的特性中获益,因为该队列生命周期极短,且专属于特定客户端,应用有能力重新声明其拓扑。
如果某些队列不需要复制,请在继续之前从其策略中删除经典队列镜像相关的键。这些键是
"ha-mode""ha-params""ha-promote-on-shutdown""ha-promote-on-failure""ha-sync-mode""ha-sync-batch-size"
此类非复制经典队列将作为经典队列迁移到绿色集群,从而避免在不必要的地方使用仲裁队列。
详细迁移计划
该计划已在 RabbitMQ 蓝色 3.13.7 和绿色 4.1.2 版本上进行了测试。TLS、mTLS 和普通 TCP 均已测试。无论是 CLI rabbitmqadmin v2 还是 RabbitMQ 在所有测试配置中都能正常工作。
从蓝色集群导出定义
在此步骤中,我们将备份所有 RabbitMQ 对象(不包括消息)到一个 JSON 文件中。所有用户名、虚拟主机 (vhost)、队列、权限、绑定、策略、参数、交换机,所有内容都将导出到 JSON 文件。可以应用转换来排除某些数据。此转换功能将极大帮助 CMQ 到 QQ 的迁移。
以下命令导出 RabbitMQ 的定义文件,并通过移除已弃用的 CMQ 策略键和可选队列参数来转换结果,将之前镜像的经典队列替换为仲裁队列。
任何应用了镜像策略的队列都将在定义(备份)文件中自动转换为仲裁队列。如果由于这些转换导致策略变为空(因为它仅包含 CMQ 键),它也将被删除,因为 RabbitMQ 中无法导入空策略。
RabbitMQ 定义在所有集群节点之间共享,因此,只需在一个节点上运行此命令即可。
# Export definitions from the original cluster into a file and applies two transformations
# to remove all traces of classic mirrored queues
rabbitmqadmin --host blue definitions export --file blue.json -t prepare_for_quorum_queue_migration,drop_empty_policies
导入定义
在此步骤中,我们将把上一步的定义文件(CMQ 已转换为 QQ)“恢复”到新的“绿色”集群中。此命令允许的配置选项不多,这是一个非常直接的步骤。
# Import definitions into the new (Green) cluster
rabbitmqadmin --host green definitions import --file ./blue.json
在绿色集群中配置联邦上游
在此步骤中,我们将配置队列联邦。这些步骤至关重要,因为正是队列联邦链接将把任何现有消息从原始集群传输到新集群,从而允许稍后将应用程序迁移到新集群。
请花点时间通读并提前准备好命令。
创建联邦上游
对于每个虚拟主机,在绿色集群中创建一个联邦上游
rabbitmqadmin --host green federation declare_upstream_for_queues --name cmq-qq-migration --uri 'amqp://<federation-user>:<federation-password>@blue.rabbit'
其中 <federation-user> 是蓝色 RabbitMQ 中的现有用户。建议为联邦创建一个专用用户。
其中 <federation-password> 是在蓝色 RabbitMQ 中进行身份验证所使用的联邦用户密码。
创建覆盖策略
此步骤创建“覆盖”策略,以启用所有已匹配策略的队列的队列联邦。请注意,覆盖策略是 rabbitmqadmin v2 的概念,而非 RabbitMQ HTTP API,因此此操作可以在 3.13.x 节点上执行。
在此步骤中,我们配置 RabbitMQ 使用联邦队列。当本地消费者请求消息且本地队列为空时,联邦队列会从上游集群传输消息。这将允许您将消费者应用程序迁移到绿色集群,而不会中断您的运营。
对于每个策略,创建一个策略覆盖以利用联邦上游
rabbitmqadmin --host green policies list
# => ┌──────┬─────────┬───────────────────────┬──────────┬──────────┬──────────────────┐
# => │ name │ vhost │ pattern │ apply_to │ priority │ definition │
# => ├──────┼─────────┼───────────────────────┼──────────┼──────────┼──────────────────┤
# => │ ha │ finance │ (?:^po$)|(?:.*\.dlx$) │ all │ 0 │ max-length: 100 │
# => │ │ │ │ │ │ queue-version: 2 │
# => │ │ │ │ │ │ │
# => └──────┴─────────┴───────────────────────┴──────────┴──────────┴──────────────────┘
rabbitmqadmin --host green policies declare_override --name ha --definition '{"federation-upstream": "cmq-qq-migration"}'
接下来,为任何未被现有策略匹配的队列创建一个全面/覆盖性策略 (blanket/catch-all policy)
rabbitmqadmin --host green policies declare_blanket --name cmq-qq-migration_blanket --apply-to queues --definition '{"federation-upstream": "cmq-qq-migration"}'
这将确保所有队列在蓝色和绿色集群之间进行联邦。
验证联邦功能是否正常
一旦策略创建完成,绿色集群中的联邦插件将创建到上游(蓝色)的链接(连接)。链接的存在确保了当消费者应用程序从蓝色迁移到绿色时,联邦队列准备好将消息从上游(蓝色)移动到绿色集群中。
rabbitmqadmin --host green federation list_all_links
# => ┌──────────────┬─────────┬──────────┬─────────────────────┬─────────┬───────┬──────────────────┬──────────────────────────────────┐
# => │ node │ vhost │ id │ uri │ status │ type │ upstream │ consumer_tag │
# => ├──────────────┼─────────┼──────────┼─────────────────────┼─────────┼───────┼──────────────────┼──────────────────────────────────┤
# => │ rabbit@green │ finance │ 8f0f0d8a │ amqps://blue.rabbit │ running │ queue │ cmq-qq-migration │ federation-link-cmq-qq-migration │
# => ├──────────────┼─────────┼──────────┼─────────────────────┼─────────┼───────┼──────────────────┼──────────────────────────────────┤
# => │ rabbit@green │ finance │ 01efa5b5 │ amqps://blue.rabbit │ running │ queue │ cmq-qq-migration │ federation-link-cmq-qq-migration │
# => ├──────────────┼─────────┼──────────┼─────────────────────┼─────────┼───────┼──────────────────┼──────────────────────────────────┤
# => │ rabbit@green │ finance │ 950faf11 │ amqps://blue.rabbit │ running │ queue │ cmq-qq-migration │ federation-link-cmq-qq-migration │
# => └──────────────┴─────────┴──────────┴─────────────────────┴─────────┴───────┴──────────────────┴──────────────────────────────────┘
准备迁移消费者
两个 RabbitMQ 集群(蓝色和绿色)都已准备好支持迁移。应用程序迁移的第一步是将消费者应用程序迁移到新的 RabbitMQ 集群(绿色)。
如何将应用程序部署到绿色集群完全取决于您如何部署和运行 RabbitMQ 以及应用程序。无需匆忙完成此步骤。在进行下一步之前,请确保您的消费者应用程序按预期工作。
准备迁移生产者
这是完成迁移的最后一步!迁移生产者包括停止生产者应用程序,让消费者通过队列联邦清空蓝色集群中的队列,然后启动绿色集群中的生产者应用程序。
如果您无法负担在消费者清空队列之前停止生产者应用程序的后果,还有一种特殊情况。如果您的使用场景包含非常长的积压队列和缓慢的消费者,就会出现这种情况。在这种特殊情况下,除了设置队列联邦外,请考虑在将生产者应用程序重新部署到绿色集群之前,声明一个或多个 Shovel,用于将消息从蓝色移动到绿色。
迁移后的清理工作
确认迁移成功并且您的应用程序运行没有问题后,请继续清理为迁移需求而声明的策略,以及绿色集群中的联邦上游。
删除临时策略
在此过程中创建的覆盖策略都将以 override. 为前缀。在本指南中,全面/覆盖性策略的名称是 cmq-qq-migration_blanket
rabbitmqadmin --host green policies list
# => ┌──────────────────────────┬─────────┬───────────────────────┬──────────┬──────────┬─────────────────────────────────────────┐
# => │ name │ vhost │ pattern │ apply_to │ priority │ definition │
# => ├──────────────────────────┼─────────┼───────────────────────┼──────────┼──────────┼─────────────────────────────────────────┤
# => │ cmq-qq-migration_blanket │ finance │ .* │ queues │ -21 │ federation-upstream: "cmq-qq-migration" │
# => │ │ │ │ │ │ │
# => ├──────────────────────────┼─────────┼───────────────────────┼──────────┼──────────┼─────────────────────────────────────────┤
# => │ ha │ finance │ (?:^po$)|(?:.*\.dlx$) │ all │ 0 │ max-length: 100 │
# => │ │ │ │ │ │ queue-version: 2 │
# => │ │ │ │ │ │ │
# => ├──────────────────────────┼─────────┼───────────────────────┼──────────┼──────────┼─────────────────────────────────────────┤
# => │ overrides.ha │ finance │ (?:^po$)|(?:.*\.dlx$) │ all │ 100 │ federation-upstream: "cmq-qq-migration" │
# => │ │ │ │ │ │ max-length: 100 │
# => │ │ │ │ │ │ queue-version: 2 │
# => │ │ │ │ │ │ │
# => └──────────────────────────┴─────────┴───────────────────────┴──────────┴──────────┴─────────────────────────────────────────┘
rabbitmqadmin --host green policies delete --name cmq-qq-migration_blanket
rabbitmqadmin --host green policies delete --name overrides.ha
接下来,删除用于迁移消息的联邦上游
rabbitmqadmin --host green federation delete_upstream --name cmq-qq-migration
在开发环境中测试迁移
起初,这种迁移可能看起来令人望而生畏。RabbitMQ 团队对不同的场景和不同的队列特性组合进行了广泛的测试,然而,没有什么比亲自在自己的环境中进行尝试更能增加信心了!
因此,强烈建议在开发或暂存环境中测试所有的迁移命令和计划。有时很难让您自己的应用程序模拟生产环境中的负载。对于这种情况,RabbitMQ Perf Test 是模拟工作负载和测试特定 RabbitMQ 特性的绝佳选择。
例如,要模拟一个生产者以 30 条/秒的速率向交换机 inc 发送带有路由键 bills 的消息,您可以使用以下 perf-test 命令
java -jar perf-test.jar \
-y 0 -x 1 -c 10 -p -qq \
--rate 30 -e inc \
--routing-key 'bills' \
--uri "amqp://myuser:mypass@blue.rabbit.example.com"
所有的 perf-test 选项都记录在 Perf Test 文档及其帮助命令中
java -jar perf-test.jar --help
迁移后检查清单
迁移完成后,以下列表可以帮助双重检查迁移是否成功。欢迎添加任何在您的环境中适用的额外项目。
- 所有消费者应用程序都已连接到绿色(新)RabbitMQ 集群
- 所有生产者应用程序都已连接到绿色(新)RabbitMQ 集群
- 不存在临时策略
- 不存在联邦上游
- RabbitMQ 没有有效的报警
- 所有指标都在其典型范围内
迁移陷阱
TLS
rabbitmqadmin v2 支持启用 TLS 的连接。但是,它要求受信任的 CA 是系统受信任 CA 的一部分。
如何将受信任的 CA 添加到系统钥匙串取决于操作系统。以下链接并非完整的参考,但为最常见的系统提供了方便
一旦 CA 被添加到操作系统级别的受信任证书列表中,它们的 PEM 格式捆绑文件就可以与 rabbitmqadmin 一起使用。例如
rabbitmqadmin --use-tls --tls-ca-cert-file /path/to/your/chained_ca_certificate.pem queues list
如果您配置 RabbitMQ 使用相互对等验证 (mTLS),rabbitmqadmin 也需要提供客户端证书和密钥对。
例如:
rabbitmqadmin --use-tls --tls-ca-cert-file /path/to/your/chained_ca_certificate.pem \
--tls-cert-file /path/to/your/client_certificate.pem \
--tls-key-file /path/to/your/client_key.pem \
queues list
或者,由于将所有 TLS 选项添加到每个命令可能很繁琐,可以在 rabbitmqadmin 配置文件中配置所有 TLS 选项、主机名、端口和凭据。rabbitmqadmin 的配置文件为 TOML 格式,除了“节点”别名外,它还接受一些选项。例如
[blue]
hostname = "blue.rabbit"
tls = true
ca_certificate_bundle_path = "/path/to/your/chained_ca_certificate.pem"
client_certificate_file_path = "/path/to/your/client_certificate.pem"
client_private_key_file_path = "/path/to/your/client_key.pem"
port = 32795
[green]
hostname = "green.rabbit"
port = 32811
tls = true
ca_certificate_bundle_path = "/path/to/your/chained_ca_certificate.pem"
client_certificate_file_path = "/path/to/your/client_certificate.pem"
client_private_key_file_path = "/path/to/your/client_key.pem"
将上述配置与 --config 和 --node 结合使用。例如 rabbitmqadmin --config rabbitmqadmin.toml --node blue queues list。
结论
仲裁队列 (QQ) 是 RabbitMQ 中实现数据安全的首选队列类型。它们提供可预测的故障转移行为和比经典镜像队列 (CMQ) 更高的吞吐量。仲裁队列不断获得更新、性能改进和错误修复,而经典镜像队列在 3.13 版本中处于维护状态,并在 RabbitMQ 4 中被完全移除。经典队列(无镜像)仍然是一种有效且受支持的队列类型。经典队列对于某些用例仍然是一个很好的选择,例如:RPC 模式。或任何不需要高可用性的用例。
有了新一代的 rabbitmqadmin,现在比以往任何时候都更容易从镜像经典队列迁移到仲裁队列,并同时升级到最新的支持的 RabbitMQ 发布系列。

