特性标志
概述
在混合版本集群中(例如,某些版本是 3.11.x,而某些版本是 3.12.x),在升级期间,某些节点将支持不同的功能集,在某些场景下行为不同,否则行为不完全相同:毕竟它们是不同的版本。
特性标志是一种机制,用于控制哪些特性被认为在所有集群节点上启用或可用。如果启用了特性标志,则其关联的特性(或行为)也会启用。否则,集群中的所有节点都将禁用该特性(行为)。
特性标志子系统允许具有不同版本的 RabbitMQ 节点确定它们是否兼容,然后在具有不同版本的情况下进行通信,从而可能具有不同的功能集或实现细节。
引入此子系统是为了允许对集群成员进行滚动升级,而无需关闭整个集群。
特性标志并非旨在用作集群配置的一种形式。在成功完成滚动升级后,用户应启用所有特性标志。
所有特性标志在某个时候都会变为强制性(毕业)。例如,RabbitMQ 3.12 要求在升级之前启用 3.11 系列中引入的特性标志,RabbitMQ 3.11 毕业了所有 3.8 标志,依此类推。
快速总结 (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 中的“管理 > 特性标志”列出和启用特性标志
如何禁用特性标志
一旦启用,就无法禁用特性标志。
如何覆盖初始启动时要启用的特性标志列表
默认情况下,新的未集群节点将在启用所有稳定特性标志的情况下启动,但是可以覆盖此设置。
由于启用后,特性标志无法禁用,因此使用此特性仅对启用更多标志是安全的。当然,提供与当前启用的列表相同的标志列表也是安全的。
此机制仅在用户希望使用运行较新版本的 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 捆绑在一起的一级插件之一提供。
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 使用单个标志来控制多个特性和更改。如果升级到 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 订阅者的伪队列类型,省略队列进程 |
特性标志如何工作?
从操作员的角度来看
节点和版本兼容性
操作员必须考虑特性标志的情况有两种
节点将其自身的特性标志列表与远程节点的特性标志列表进行比较,以确定是否可以加入集群。规则定义为
- 本地启用的所有特性标志必须在远程受支持。
- 远程启用的所有特性标志必须在本地受支持。
重要的是要理解启用和支持之间的区别
- 支持的特性标志是节点已知的特性标志。它可以启用或禁用,但其状态在此时无关紧要。
- 启用的特性标志是节点激活和使用的特性标志。根据上面的定义,它隐式地是一个受支持的特性标志。
如果这两个条件之一未得到验证,则节点无法加入或重新加入集群。
但是,如果它可以加入集群,则启用的特性标志的状态将在节点之间同步:如果在一个节点上启用了特性标志,则在所有其他节点上也会启用它。
特性标志的范围
特性标志子系统仅涵盖节点间通信。这意味着以下场景未涵盖,并且可能无法按最初预期的方式工作。
在远程节点上使用 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 节点,则由于 HTTP API 更改,在浏览器中运行的 JavaScript 代码可能会因异常而失败。
启用特性标志时会发生什么
当使用 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.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.
如何调整和运行混合版本集群的测试套件
当一个功能或行为依赖于一个功能标志(在核心 Broker 或插件中)时,相关的测试套件必须进行调整以考虑这个功能标志。这意味着在运行实际的测试用例之前,设置代码必须验证是否支持该功能标志,如果支持则启用它,否则跳过该测试用例。这对于在组或套件级别运行的设置代码也是一样的。
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-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 启动时禁用所有功能标志:这对于使较新节点与较旧节点兼容是强制性的。