功能标志
概述
在升级过程中,如果集群处于混合版本状态(例如,部分节点为 3.11.x,部分为 3.12.x),某些节点会支持不同的功能集,在特定场景下的行为也有所不同:毕竟它们属于不同的版本。
功能标志是一种控制集群节点上哪些功能被视为启用或可用的机制。如果启用了某个功能标志,则其关联的功能(或行为)也将启用。反之,集群中的所有节点都将禁用该功能(行为)。
功能标志子系统允许不同版本的 RabbitMQ 节点确定彼此是否兼容,并在版本不同(可能导致功能集或实现细节不同)的情况下进行通信。
引入此子系统是为了允许在滚动升级集群成员时无需关闭整个集群。
功能标志不应作为集群配置的一种形式使用。在成功完成滚动升级后,用户应启用所有功能标志。
所有功能标志在某个阶段都会变得强制执行(毕业)。例如,RabbitMQ 4.2 要求在升级前必须启用 3.13 系列引入的功能标志,RabbitMQ 4.3 要求启用 4.2 的所有功能标志,以此类推。
快速摘要 (TL;DR)
功能标志基本规则
- 只有当集群中所有节点都支持时,才能启用功能标志
- 节点加入或重新加入集群的条件是:
- 它支持集群中已启用的所有功能标志,且
- 集群中其他所有成员也支持该节点上已启用的所有功能标志
- 功能标志一旦启用,就无法禁用
例如,只要未启用 3.13.x 特有的功能标志,RabbitMQ 3.13.x 和 3.12.x 节点就是兼容的。
关键 CLI 工具命令
- 列出功能标志
rabbitmqctl list_feature_flags
- 启用某个功能标志(或所有当前已禁用的标志)
rabbitmqctl enable_feature_flag <all | name>
也可以通过 管理插件 UI 在“Admin > Feature flags”中列出并启用功能标志。
示例
示例 1:兼容节点
- 如果节点 A 和 B 未组成集群,它们可以组成集群。
- 如果节点 A 和 B 已组成集群
- 可以启用“咖啡机”。
- 不能启用“榨汁机”,因为节点 B 不支持它。
示例 2:不兼容节点
- 如果节点 A 和 B 未组成集群,它们无法组成集群,因为节点 B 不支持“榨汁机”。
- 如果节点 A 和 B 已组成集群,且在节点 B 停止时启用了“榨汁机”,则节点 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_dbRABBITMQ_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 捆绑的一级插件提供。
Required 列显示了必须在升级之前启用该功能标志的 RabbitMQ 版本。例如,如果一个功能标志在 3.12.0 中是必需的,则在升级到 3.12.x 之前,必须在 3.11.x(或更早版本)中启用该功能标志。否则,如果 RabbitMQ 节点在禁用此功能标志的情况下升级到 3.12.x,该节点将拒绝以 3.12.x 版本启动。
Stable 列显示了引入该功能标志的 RabbitMQ 版本。例如,如果一个功能标志在 3.11.0 中是稳定的,则在将 RabbitMQ 集群中的所有节点升级到 3.11.x 版本后,应立即启用该功能标志。
核心功能标志
以下功能标志由 RabbitMQ 核心提供。
下面列出的大多数功能标志描述都非常简短。这是因为大多数功能标志仅用于避免在混合版本集群中执行潜在的不安全操作,或校正必须在所有集群节点间保持一致的行为等。
khepri_db 是一个例外,因为其作用范围。
| Required (必需) | Stable (稳定) | 功能标志名称 | 描述 |
|---|---|---|---|
| 4.0 | rabbitmq_4.0.0 | 启用 RabbitMQ 4.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 之前,请务必先在非生产环境中使用适当的工作负载对其进行测试。 | |
| 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 提供。
| Required (必需) | Stable (稳定) | 功能标志名称 | 描述 |
|---|---|---|---|
| 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 提供。
| Required (必需) | Stable (稳定) | 功能标志名称 | 描述 |
|---|---|---|---|
| 4.0.0 | 3.13.0 | mqtt_v5 | 支持 MQTT 5.0 |
| 4.0.0 | 3.12.0 | delete_ra_cluster_mqtt_node | 删除 Ra 集群 mqtt_node,因为 MQTT 客户端 ID 是在本地跟踪的 |
| 4.0.0 | 3.12.0 | rabbit_mqtt_qos0_queue | 支持 MQTT QoS 0 订阅者的伪队列类型,无需队列进程 |
功能标志是如何工作的?
从操作员角度来看
节点和版本兼容性
操作员有两个时间点需要考虑功能标志
节点通过将自己的功能标志列表与远程节点的功能标志列表进行比较,来确定它是否可以加入集群。规则定义如下:
- 本地启用的所有功能标志必须在远程得到支持。
- 远程启用的所有功能标志必须在本地得到支持。
理解 enabled(启用)和 supported(支持)之间的区别很重要:
- Supported(支持)的功能标志是指节点已知的标志。它可以被启用或禁用,但此时其状态无关紧要。
- Enabled(启用)的功能标志是指节点已激活并正在使用的标志。根据上面的定义,它隐式地属于 supported 功能标志。
如果这两个条件中的任何一个未得到验证,节点就无法加入或重新加入集群。
但是,如果它可以加入集群,那么enabled功能标志的状态会在节点间同步:如果一个功能标志在一个节点上被启用,它也会在所有其他节点上被启用。
功能标志的范围
功能标志子系统仅涵盖节点间通信。这意味着以下场景不在范围内,且可能不会按预期工作。
在远程节点上使用 rabbitmqctl
仅在远程节点运行与 rabbitmqctl 相同版本的 RabbitMQ 时,才支持使用 rabbitmqctl 控制远程节点。
如果对远程节点使用了来自不同次要/主要版本 RabbitMQ 的 CLI 工具,它们可能无法按预期工作,甚至可能对节点产生意想不到的副作用。
对 HTTP API 的请求进行负载均衡
如果发送到由 管理插件 公开的 HTTP API 的请求经过负载均衡器(包括来自管理插件 UI 的请求),则根据处理请求的节点的版本,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,它也应与 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.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-heleprs 中有一些辅助函数可以简化该检查。以下是一个示例,取自 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-umbrellacd secondary-umbrellagit checkout v3.12.xmake co -
在次要 Umbrella 中编译 RabbitMQ 或正在测试的插件。以
rabbitmq-federation插件为例。cd secondary-umbrella/deps/rabbitmq_federationmake 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 在启动时禁用所有功能标志:这对于使较新节点与较旧节点兼容是强制性的。