功能标志
概述
在混合版本集群(例如,一些节点是 3.11.x 版本,另一些是 3.12.x 版本)升级过程中,某些节点将支持不同的功能集,在某些场景下表现不同,或者行为不完全相同:毕竟它们是不同版本。
特性标志是一种机制,用于控制在所有集群节点上哪些功能被视为已启用或可用。如果一个特性标志被启用,那么它关联的功能(或行为)也随之启用。反之,则集群中的所有节点都会禁用该功能(行为)。
特性标志子系统允许不同版本的 RabbitMQ 节点确定它们是否兼容,然后进行通信,尽管它们具有不同的版本,因此可能具有不同的功能集或实现细节。
该子系统是为了实现滚动升级集群成员,而无需关闭整个集群而引入的。
特性标志并非用作集群配置的一种形式。成功滚动升级后,用户应启用所有特性标志。
所有特性标志最终都会变得强制(毕业)。例如,RabbitMQ 3.12 要求在升级前启用 3.11 系列中引入的特性标志,RabbitMQ 3.11 毕业了所有 3.8 的标志,依此类推。
快速摘要 (TL;DR)
特性标志基本规则
- 只有当集群中的所有节点都支持时,才能启用一个特性标志。
- 节点只有在满足以下条件时才能加入或重新加入集群:
- 它支持集群中启用的所有特性标志;并且
- 集群中的其他所有成员都支持该节点上启用的所有特性标志。
- 一旦启用,特性标志就不能被禁用。
例如,RabbitMQ 3.13.x 和 3.12.x 节点是兼容的,只要没有启用 3.13.x 特有的特性标志。
关键 CLI 工具命令
- 列出特性标志
rabbitmqctl list_feature_flags - 启用一个特性标志(或所有当前禁用的标志)。
rabbitmqctl enable_feature_flag <all | name>
也可以从管理插件 UI 的“Admin > Feature flags”中列出和启用特性标志。
示例
示例 1:兼容节点
- 如果节点 A 和 B 没有集群,它们可以被集群。
- 如果节点 A 和 B 已经集群
- “Coffee maker” 可以被启用。
- “Juicer machine” 不能被启用,因为它不受节点 B 的支持。
示例 2:不兼容节点
- 如果节点 A 和 B 没有集群,它们就不能被集群,因为节点 B 不支持“Juicer machine”。
- 如果节点 A 和 B 已经集群,并且在节点 B 停止时启用了“Juicer machine”,那么节点 B 在重启后就无法重新加入集群。
特性标志与 RabbitMQ 版本
如前所述,特性标志子系统的主要目标是尽可能允许集群成员在不考虑版本的情况下进行升级。
特性标志使得安全地执行滚动升级到下一个补丁版本或次要版本成为可能,除非发行说明另有说明。确实,有些更改无法作为特性标志来实现。
但是,请注意,只支持从一个次要版本升级到下一个次要或主要版本。例如,要从 3.9.16 升级到 3.12.3,需要先升级到 3.9.29,然后到最新的 3.10 补丁版本,然后到最新的 3.11 版本,最后到 3.12.3。在升级过程中的某些步骤之后,还需要启用该版本中所有可用的稳定特性标志。例如,3.12.0 是一个版本,它要求在节点升级到该版本之前启用所有特性标志。
同样,如果使用版本和下一个主要版本之间存在一个或多个次要版本分支,也可能适用。这可能有效(即,主要版本之间可能没有不兼容的更改),但出于以下原因,此场景在设计上不受支持:
- 跳过次要版本未在 CI 中进行测试。
- 非顺序发布的版本可能支持也可能不支持相同的特性标志集。存在于多个次要分支中的特性标志可能被标记为必需,并且其关联的功能/行为现在默认隐式启用。兼容性代码在此过程中被移除,阻止了与旧节点的集群。请记住,它们的目的在于允许升级,而不是作为配置机制。
特性标志的生命周期没有通用策略。例如,无法保证特性标志会在 N 个次要版本后从“稳定”变为“必需”。由于新代码建立在现有代码之上,因此在需要时,特性标志会被标记为必需,并且兼容性代码会被移除。
如何列出支持的特性标志
当节点首次启动时,所有稳定的特性标志默认都已启用。当节点升级到新版本的 RabbitMQ 时,新的特性标志将保持禁用状态。
要列出特性标志,请使用 rabbitmqctl list_feature_flags
rabbitmqctl list_feature_flags
# => Listing feature flags ...
# => name state
# => empty_basic_get_metric enabled
# => implicit_default_bindings enabled
# => quorum_queue enabled
为了提高表格的可读性,请切换到 pretty_table 格式化程序。
rabbitmqctl -q --formatter pretty_table list_feature_flags \
name state provided_by desc doc_url
这将生成一个如下所示的表。
┌───────────────────────────┬─────────┬───────────────────────────┬───────┬────────────┐
│ name │ state │ provided_by │ desc │ doc_url │
├───────────────────────────┼─────────┼───────────────────────────┼───────┼────────────┤
│ empty_basic_get_metric │ enabled │ rabbitmq_management_agent │ (...) │ │
├───────────────────────────┼─────────┼───────────────────────────┼───────┼────────────┤
│ implicit_default_bindings │ enabled │ rabbit │ (...) │ │
├───────────────────────────┼─────────┼───────────────────────────┼───────┼────────────┤
│ quorum_queue │ enabled │ rabbit │ (...) │ http://... │
└───────────────────────────┴─────────┴───────────────────────────┴───────┴────────────┘
如上例所示,list_feature_flags 命令接受要显示的列列表。可用列包括:
name:特性标志的名称。state:如果特性标志已启用或禁用,则为 _enabled_ 或 _disabled_;如果集群中的一个或多个节点不认识该特性标志(因此无法启用),则为 _unsupported_。provided_by:提供特性标志的 RabbitMQ 组件或插件。desc:特性标志的描述。doc_url:指向一个网页的 URL,用于了解有关特性标志的更多信息。stability:指示特性标志是 _required_(必需)、_stable_(稳定)还是 _experimental_(实验性)。
如何启用特性标志
在升级了集群中的所有节点后,就可以启用新的特性标志。请注意,一旦启用了新的特性标志,就无法回滚版本或添加使用旧版本的集群成员。
要启用一个特性标志,请使用
rabbitmqctl enable_feature_flag <name>
要启用所有稳定的特性标志,请使用
rabbitmqctl enable_feature_flag all
rabbitmqctl enable_feature_flag all 命令仅启用稳定的特性标志,而不启用实验性标志。
可以再次使用 list_feature_flags 命令来验证特性标志的状态。假设最初所有特性标志都已禁用,以下是启用 quorum_queue 特性标志后的状态。
rabbitmqctl -q --formatter pretty_table list_feature_flags
┌───────────────────────────┬──────────┐
│ name │ state │
├───────────────────────────┼──────────┤
│ empty_basic_get_metric │ disabled │
├───────────────────────────┼──────────┤
│ implicit_default_bindings │ disabled │
├───────────────────────────┼──────────┤
│ quorum_queue │ enabled │
└───────────────────────────┴──────────┘
也可以从管理插件 UI 的“Admin > Feature flags”中列出和启用特性标志。
如何禁用特性标志
一旦特性标志被启用,就不可能禁用它。
如何覆盖初始启动时要启用的特性标志列表
默认情况下,新且未集群的节点将以所有稳定特性标志启用状态启动,但此设置可以被覆盖。
由于已启用的特性标志无法被禁用,因此使用此功能仅可安全地用于启用更多标志。提供与当前启用列表相同的标志列表当然也是安全的。
此机制仅用于允许用户使用运行比集群其他节点更新版 RabbitMQ 的节点来扩展现有集群。兼容性仍然会进行验证,如果新节点不兼容,则将其添加到集群可能会失败。
有两种方法可以实现此目的:
-
使用
RABBITMQ_FEATURE_FLAGS环境变量# enables all feature flags in 4.0.6 except for khepri_db
RABBITMQ_FEATURE_FLAGS="delete_ra_cluster_mqtt_node,virtual_host_metadata,stream_single_active_consumer,quorum_queue,classic_mirrored_queue_version,rabbit_mqtt_qos0_queue,implicit_default_bindings,empty_basic_get_metric,'rabbitmq_4.0.0',message_containers,user_limits,queue_master_locator,detailed_queues_endpoint,stream_sac_coordinator_unblock_group,stream_update_config_command,stream_queue,stream_filtering,rabbit_exchange_type_local_random,quorum_queue_non_voters,tracking_records_in_ets,direct_exchange_routing_v2,amqp_address_v1,transient_nonexcl_queues,message_containers_deaths_v2,classic_queue_mirroring,management_metrics_collection,maintenance_mode_status,listener_records_in_ets,feature_flags_v2,global_qos,classic_queue_type_delivery_support,mqtt_v5,ram_node_type,drop_unroutable_metric,restart_streams" -
使用
advanced.config中的forced_feature_flags_on_init设置。{rabbit, [
%% enables all feature flags in 4.0.6 except for khepri_db
{forced_feature_flags_on_init, [
maintenance_mode_status,
direct_exchange_routing_v2,
user_limits,
transient_nonexcl_queues,
amqp_address_v1,stream_filtering,
implicit_default_bindings,
quorum_queue_non_voters,
'rabbitmq_4.0.0',
tracking_records_in_ets,
delete_ra_cluster_mqtt_node,
classic_queue_type_delivery_support,
restart_streams,
message_containers_deaths_v2,
feature_flags_v2,empty_basic_get_metric,
classic_queue_mirroring,
rabbit_exchange_type_local_random,
detailed_queues_endpoint,
stream_queue,
classic_mirrored_queue_version,
quorum_queue,
management_metrics_collection,
message_containers,
ram_node_type,
stream_sac_coordinator_unblock_group,
drop_unroutable_metric,
stream_single_active_consumer,
virtual_host_metadata,
listener_records_in_ets,
stream_update_config_command,
global_qos,
queue_master_locator,
rabbit_mqtt_qos0_queue,mqtt_v5
]}
]},
%% ...
环境变量优先于配置参数。
显然,必需的特性标志将始终启用,而无论此设置如何。
特性标志成熟和毕业过程
在最初引入 RabbitMQ 后,特性标志是可选的,也就是说,它们仅用于允许安全的滚动集群升级。
然而,随着时间的推移,功能变得更加成熟,RabbitMQ 的未来开发假定一套特定的功能是可用的,并且用户和开发人员都可以依赖它们。当这种情况发生时,特性标志将毕业为下一个次要版本中的核心(必需)功能。
在执行滚动集群升级后,启用所有特性标志非常重要:将来这些标志将成为强制性的,主动启用它们将有助于提高未来的升级体验。
特性标志列表
下面列出的特性标志由 RabbitMQ 核心或随 RabbitMQ 一起捆绑的 tier-1 插件提供。
Required 列显示了一个特性标志必须在哪个 RabbitMQ 版本之前启用。例如,如果一个特性标志在 3.12.0 中是必需的,那么在升级到 3.12.x 之前,必须在 3.11.x(或更早版本)中启用该特性标志。否则,如果在该特性标志禁用时将 RabbitMQ 节点升级到 3.12.x,则 RabbitMQ 节点将拒绝在 3.12.x 中启动。
Stable 列显示了引入特性标志的 RabbitMQ 版本。例如,如果一个特性标志在 3.11.0 中是稳定的,那么在将 RabbitMQ 集群中的所有节点升级到 3.11.x 版本后,应该尽快启用该特性标志。
核心特性标志
以下特性标志由 RabbitMQ 核心提供。
下面列出的大多数特性标志的描述都非常简短。这是因为大多数特性标志仅用于避免混合版本集群中的潜在不安全操作,纠正所有集群节点之间必须一致的行为等等。
khepri_db 是一个例外,因为它范围广。
| 必需 | 稳定 | 特性标志名称 | 描述 |
|---|---|---|---|
| 4.0 | rabbitmq_4.0.0 | 启用 RabbitMQ 4.0 中引入的多个功能和更改。 RabbitMQ 4.0 在启用此特性标志之前,将以向后兼容模式运行。例如,新的仲裁队列功能和新的 AMQP-1.0 流控制机制将不可用。 | |
| 4.0 | khepri_db | 启用Khepri,一个基于 Raft 的模式数据存储,与 Mnesia 相比,具有更优越(尤其是更可预测)的节点和网络故障恢复特性。 信息 从 RabbitMQ 4.0 开始,Khepri (与 Mnesia 一样)得到完全支持。由于其范围,此特性标志必须显式启用(选择加入)。 重要 由于 RabbitMQ 4.0 中 Khepri 模式的重大更改,已启用 Khepri 的 3.13.x 集群将无法就地升级到 4.0。此类集群应使用蓝绿部署升级策略。 在生产环境中采用 Khepri 之前,请务必先在非生产环境中对 Khepri 进行适当的工作负载测试。 | |
| 4.0 | rabbit_exchange_type_local_random | 由本地随机交换器使用。 | |
| 3.13.1 | quorum_queue_non_voters | 支持非投票仲裁队列副本状态。 | |
| 3.13.0 | message_containers | 启用内部使用的基于 AMQP 1.0 的新消息格式。 | |
| 3.13.0 | detailed_queues_endpoint | 引入 | |
| 4.0.0 | 3.13.0 | stream_filtering | 流过滤支持。 |
| 4.0.0 | 3.13.0 | stream_update_config_command | 从可以为流动态更新的策略键列表中删除 |
| 4.0.0 | 3.12.0 | restart_streams | 支持使用可选的首选下一个领导者参数重启流。用于实现流领导者重新平衡。 |
| 4.0.0 | 3.12.0 | stream_sac_coordinator_unblock_group | 错误修复,用于解封超级流分区中的一组消费者。 |
| 3.12.0 | 3.11.0 | direct_exchange_routing_v2 | v2 直接交换路由实现。 |
| 3.12.0 | 3.11.0 | feature_flags_v2 | 特性标志子系统 v2。 |
| 3.12.0 | 3.11.0 | listener_records_in_ets | 将监听器记录存储在 ETS 中,而不是 Mnesia 中。 |
| 3.12.0 | 3.11.0 | stream_single_active_consumer | 流的单一活动消费者。 |
| 3.12.0 | 3.11.0 | tracking_records_in_ets | 将跟踪记录存储在 ETS 中,而不是 Mnesia 中。 |
| 3.12.0 | 3.10.9 | classic_queue_type_delivery_support | 错误修复,用于使用混合版本的经典队列传递。 |
| 3.12.0 | 3.9.0 | stream_queue | 支持 stream 类型的队列。 |
| 3.11.0 | 3.8.10 | user_limits | 配置用户的连接和通道限制。 |
| 3.11.0 | 3.8.8 | maintenance_mode_status | 维护模式状态。 |
| 3.11.0 | 3.8.0 | implicit_default_bindings | 默认绑定现在是隐式的,而不是存储在数据库中。 |
| 3.11.0 | 3.8.0 | quorum_queue | 支持 quorum 类型的队列。 |
| 3.11.0 | 3.8.0 | virtual_host_metadata | 虚拟主机元数据(描述、标签等)。 |
rabbitmq_management_agent 特性标志
以下特性标志由插件 rabbimq_management_agent 提供。
| 必需 | 稳定 | 特性标志名称 | 描述 |
|---|---|---|---|
| 3.12.0 | 3.8.10 | drop_unroutable_metric | 在统计中计算要丢弃的不可路由发布。 |
| 3.12.0 | 3.8.10 | empty_basic_get_metric | 在统计中计算对空队列的 AMQP basic.get。 |
rabbitmq_rabbitmq_mqtt 特性标志
以下特性标志由插件 rabbimq_mqtt 提供。
| 必需 | 稳定 | 特性标志名称 | 描述 |
|---|---|---|---|
| 3.13.0 | mqtt_v5 | 支持 MQTT 5.0。 | |
| 3.12.0 | delete_ra_cluster_mqtt_node | 删除 Ra 集群 mqtt_node,因为 MQTT 客户端 ID 是本地跟踪的。 | |
| 3.12.0 | rabbit_mqtt_qos0_queue | 支持 MQTT QoS 0 订阅者的伪队列类型,省略了队列进程。 |
特性标志如何工作?
从操作员角度看
节点和版本兼容性
操作员需要在两个时间点考虑特性标志:
节点将其自身的特性标志列表与远程节点的特性标志列表进行比较,以确定它是否可以加入集群。规则定义如下:
- 本地启用的所有特性标志必须得到远程支持。
- 远程启用的所有特性标志必须得到本地支持。
理解“enabled”(启用)和“supported”(支持)之间的区别很重要。
- 支持的特性标志是节点已知的标志。它可以被启用或禁用,但此时其状态无关紧要。
- 启用的特性标志是节点已激活并使用的标志。根据上述定义,它隐式地是一个支持的特性标志。
如果这两个条件之一未经验证,则节点无法加入或重新加入集群。
但是,如果它可以加入集群,则启用的特性标志的状态会在节点之间同步:如果一个特性标志在一个节点上启用,它将在所有其他节点上启用。
特性标志的范围
特性标志子系统仅涵盖节点间通信。这意味着以下场景未被涵盖,并且可能无法按预期工作。
在远程节点上使用 rabbitmqctl
仅当远程节点运行的 RabbitMQ 版本与 rabbitmqctl 版本相同时,才支持使用 rabbitmqctl 控制远程节点。
如果从不同次要/主要版本的 RabbitMQ 使用的 CLI 工具在远程节点上运行,它们可能会无法按预期工作,甚至对节点产生意外的副作用。
负载均衡到 HTTP API 的请求
如果通过负载均衡器(包括管理插件 UI 中的负载均衡器)发送到由管理插件公开的 HTTP API 的请求,其行为和响应可能会有所不同,具体取决于处理请求的节点版本。如果 HTTP API 的域名解析到多个 IP 地址,情况也是如此。
如果管理 UI 在浏览器中打开并定期自动刷新,在滚动升级期间可能会发生这种情况。
例如,如果管理 UI 是从 RabbitMQ 3.11.x 节点加载的,但随后查询 RabbitMQ 3.12.x 节点,那么在浏览器中运行的 JavaScript 代码可能会由于 HTTP API 的更改而引发异常。
启用特性标志时会发生什么
使用 rabbitmqctl 启用特性标志时,内部会发生以下情况:
- RabbitMQ 检查特性标志是否已启用。如果已启用,则停止。
- 它检查特性标志是否受支持。如果未受支持,则停止。
- 它将特性标志状态标记为 _state_changing_。这是一个内部过渡状态,用于通知特性标志的消费者。大多数情况下,这意味着依赖于该特定特性标志的组件将在状态更改为 _enabled_ 或 _disabled_ 之前被阻塞。
- 它启用该特性标志所依赖的所有特性标志。因此,对于它们中的每一个,我们都将经历相同的过程。
- 它执行迁移函数(如果存在)。该函数负责准备或转换各种资源,例如更改数据库的模式。
- 如果以上所有步骤都成功,则特性标志状态变为 _enabled_。否则,它将恢复为 _disabled_。
作为操作员,在此过程中最重要的是要记住,如果迁移耗时,某些组件因此RabbitMQ 中的某些操作可能会被阻塞。
从开发人员角度看
在开发插件或 RabbitMQ 核心贡献时,应使用特性标志使新版本的代码与旧版本的 RabbitMQ 兼容。
何时使用特性标志
开发人员有责任查看现有和未来的(即添加到 main 分支的)特性标志列表,看看新代码是否可以适应并利用它们。
以下是一个示例。在开发一个曾经使用 rabbit_common/include/rabbit.hrl 中定义的 #amqqueue{} 记录的插件时,该插件必须进行修改以使用新的 amqqueue API,该 API 隐藏了之前的记录(现在是私有的)。但是,没有必要查询特性标志:该插件将与 RabbitMQ 3.8.0 及更高版本 ABI 兼容(即无需重新编译)。一旦 amqqueue 出现在 3.7.x 分支中,它也应该与 RabbitMQ 3.7.x ABI 兼容。
但是,如果插件的目标是 RabbitMQ 3.8.0 中引入的仲裁队列,它可能需要查询特性标志来确定它可以做什么。例如,它可以声明一个仲裁队列吗?它甚至可以预期 amqqueue 中添加的作为仲裁队列实现一部分的新字段吗?
如果插件仔细检查特性标志以避免任何不正确的期望,它将与许多版本的 RabbitMQ 兼容:用户无需重新编译任何内容或下载另一个特定版本副本的插件。
何时声明特性标志
如果插件或核心代理更改修改了以下任一方面:
- 记录定义
- 复制数据库模式
- 节点之间传递的 Erlang 消息格式
- 从远程节点调用的模块和函数
那么与旧版本 RabbitMQ 的兼容性就成了一个问题。这时,新的特性标志可以帮助确保更平滑的升级体验。
特性标志的两个最重要的部分是:
- 声明为模块属性
- 迁移函数
声明是一个模块属性,如下所示:
-rabbit_feature_flag(
{quorum_queue,
#{desc => "Support queues of type quorum",
doc_url => "https://rabbitmq.org.cn/docs/quorum-queues",
stability => stable,
migration_fun => {?MODULE, quorum_queue_migration}
}}).
迁移函数是一个无状态函数,如下所示:
quorum_queue_migration(FeatureName, _FeatureProps, enable) ->
Tables = ?quorum_queue_tables,
rabbit_table:wait(Tables),
Fields = amqqueue:fields(amqqueue_v2),
migrate_to_amqqueue_with_type(FeatureName, Tables, Fields);
quorum_queue_migration(_FeatureName, _FeatureProps, is_enabled) ->
Tables = ?quorum_queue_tables,
rabbit_table:wait(Tables),
Fields = amqqueue:fields(amqqueue_v2),
mnesia:table_info(rabbit_queue, attributes) =:= Fields andalso
mnesia:table_info(rabbit_durable_queue, attributes) =:= Fields.
更多实现文档可以在rabbit_feature_flags 模块源代码中找到。
Erlang 的 edoc 参考可以从 RabbitMQ 仓库克隆或源代码存档本地生成。
gmake edoc
# => ... Ignore warnings and errors...
# Now open `doc/rabbit_feature_flags.html` in the browser.
如何改编和运行具有混合版本集群的测试套件
当一个特性或行为依赖于一个特性标志(无论是在核心代理中还是在插件中)时,必须相应地调整测试套件以考虑该特性标志。这意味着在运行实际测试用例之前,设置代码必须验证特性标志是否受支持,如果受支持则启用它,或者跳过该测试用例。这同样适用于在组或套件级别运行的设置代码。
rabbitmq-ct-ct-helpers 中有辅助函数来简化此检查。以下是一个示例,来自 rabbitmq-server 中的 dynamic_qq_SUITE.erl 测试套件。
init_per_testcase(Testcase, Config) ->
% (...)
% 1.
% The broker or cluster is started: we rely on this to query feature
% flags.
Config1 = rabbit_ct_helpers:run_steps(
Config,
rabbit_ct_broker_helpers:setup_steps() ++
rabbit_ct_client_helpers:setup_steps()),
% 2.
% We try to enable the `quorum_queue` feature flag. The helper is
% responsible for checking if the feature flag is supported and
% enabling it.
case rabbit_ct_broker_helpers:enable_feature_flag(Config1, quorum_queue) of
ok ->
% The feature flag is enabled at this point. The setup can
% continue to play with `Config1` and the cluster.
Config1;
Skip ->
% The feature flag is unavailable/unsupported. The setup
% calls `end_per_testcase()` to stop the node/cluster and
% skips the testcase.
end_per_testcase(Testcase, Config1),
Skip
end.
可以在混合版本集群的上下文中本地运行测试套件。如果配置为这样做,rabbitmq-ct-helpers 将在启动集群时使用第二版 RabbitMQ 来启动一半节点。
- 节点 1 将使用主副本(用于启动测试套件的版本)。
- 节点 2 将使用次要副本(显式提供给
rabbitmq-ct-helpers的版本)。 - 节点 3 将使用主副本。
- 节点 4 将使用次要副本。
- ...
要在混合版本集群的上下文中运行测试套件:
-
克隆
rabbitmq-public-umbrella仓库并检出相应的分支或标签。这将是次要 Umbrella。在此示例中,使用了v3.12.x分支。git clone https://github.com/rabbitmq/rabbitmq-server.git secondary-umbrella
cd secondary-umbrella
git checkout v3.12.x
make co -
在次要 Umbrella 中编译 RabbitMQ 或正在测试的插件。
rabbitmq-federation插件用作示例。cd secondary-umbrella/deps/rabbitmq_federation
make dist -
转到 RabbitMQ 或主副本中的同一插件。
cd /path/to/primary/rabbitmq_federation -
运行测试套件。此处,指定了两个环境变量来配置“混合版本集群”模式:
SECONDARY_UMBRELLA=/path/to/secondary-umbrella \
RABBITMQ_FEATURE_FLAGS= \
make tests第一个环境变量
SECONDARY_UMBRELLA告诉rabbitmq-ct-helpers在何处找到次要 Umbrella,顾名思义。这就是启用混合版本集群模式的方式。第二个环境变量
RABBITMQ_FEATURE_FLAGS设置为空字符串,指示 RabbitMQ 以所有特性标志禁用状态启动:这是使较新节点与旧节点兼容的强制要求。