Envoy 负载均衡器批量更新合并重建(coalesce_lb_rebuilds_on_batch_update)默认开启:原理、源码与配置指南
【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy
导读
本篇文章聚焦 Envoy 的一项运行时特性变更:envoy.reloadable_features.coalesce_lb_rebuilds_on_batch_update现在默认开启(true)。该变更让线程感知型负载均衡器(如RING_HASH、MAGLEV)在批量主机(host)更新时,只在批次结束时的单个MemberUpdateCb回调中重建一次 factory 状态,而不是在每个优先级的更新回调中重复重建。读完本文,你将理解 Envoy 主机更新的回调机制、该特性的源码实现与触发条件、测试如何验证其行为,以及如何通过 bootstrap 运行时配置回退到旧行为。
一、变更概述:一次批处理只重建一次工厂状态
按 changelogs/current/minor_behavior_changes/upstream__coalesce-lb-rebuilds-on-batch-update.rst 的说明:
The runtime guard
envoy.reloadable_features.coalesce_lb_rebuilds_on_batch_updatenow defaults totrue. A thread-aware load balancer (for exampleRING_HASHorMAGLEV) rebuilds its factory state once at the end of a batch host update, from the single end-of-cycle member-update callback, instead of once per priority from the per-priority update callback. The rebuild still lands before the cluster manager posts the update to the worker threads, so this only removes the redundant per-priority rebuilds of a batch.
核心结论有三点:
- 默认值翻转:该运行时开关由
false变为true,即新行为默认生效; - 重建次数减少:线程感知型负载均衡器(thread-aware load balancer)在一个批量主机更新(batch host update)期间,从"每个优先级各重建一次"变为"整批只在结束时重建一次";
- 时序安全不受影响:重建仍然发生在 cluster manager 将更新投递(post)到 worker 线程之前,因此只是消除了批次内冗余的逐优先级重建,不改变数据一致性语义。
若出现异常,可通过将envoy.reloadable_features.coalesce_lb_rebuilds_on_batch_update显式设为false回退到旧行为。
二、背景:Envoy 中主机更新的回调机制
要理解这次变更,先要掌握PrioritySet上两种更新回调的区别(对应源码 source/extensions/load_balancing_policies/common/thread_aware_lb_impl.cc 中的注释):
PriorityUpdateCb(逐优先级回调):每当某一个优先级(priority,如 EDS 中的 P0、P1、P2)的主机集合发生变化时触发,一次批量更新里会按优先级触发多次;MemberUpdateCb(成员更新回调):在一次批量主机更新结束时只触发一次,是"端到端"(end-of-cycle)的回调。
线程感知型负载均衡器(ThreadAwareLoadBalancerBase)采用"主线程构建 factory 状态、worker 线程快照使用"的模式:
- 主线程通过
refresh()重建 factory 状态(例如 ring hash 的环、maglev 的查找表); - worker 线程从 factory 快照读取最新的负载均衡结构;
refresh()必须赶在 cluster manager 把对应主机更新投递给 worker 线程之前完成,否则 worker 可能醒来后快照到过期的 factory(代码注释中明确引用了对应的 issue #45055)。
因此,ThreadAwareLoadBalancerBase在自己的PriorityUpdateCb中执行refresh(),因为该回调注册在 cluster manager 的投递回调之前,可以保证refresh()先于跨线程投递执行。
旧行为的问题:在批量主机更新(batch host update)场景下,PriorityUpdateCb会按优先级反复触发(每个优先级一次),导致 factory 状态在一个批次内被重复重建 N 次(N = 涉及更新的优先级数量),而这些中间状态最终都会被批次结束时的状态覆盖,属于纯粹的冗余计算。
三、变更的源码级实现
3.1 运行时开关的定义
该开关在 source/common/runtime/runtime_features.cc 中注册:
RUNTIME_GUARD(envoy_reloadable_features_coalesce_lb_rebuilds_on_batch_update);根据该文件头部的注释(第 30-32 行),RUNTIME_GUARD默认即为true,即新代码路径默认被启用;若需默认关闭则使用FALSE_RUNTIME_GUARD。本次变更正是将该 guard 从默认关闭改为默认开启。
3.2 线程感知负载均衡器的合并逻辑
thread_aware_lb_impl.cc 中ThreadAwareLoadBalancerBase::initialize()的实现如下:
const bool coalesce_lb_rebuilds = Runtime::runtimeFeatureEnabled( "envoy.reloadable_features.coalesce_lb_rebuilds_on_batch_update"); const bool batch_aware_update = Runtime::runtimeFeatureEnabled("envoy.reloadable_features.enable_batch_aware_update"); const bool defer_refresh_during_batch = coalesce_lb_rebuilds && batch_aware_update; priority_update_cb_ = priority_set_.addPriorityUpdateCb( this, defer_refresh_during_batch { // Refresh eagerly here if we didn't enable coalescing or the batch-aware update. if (!defer_refresh_during_batch) { processDirtyPriorities(); refresh(); } }); member_update_cb_ = priority_set_.addMemberUpdateCb( this, defer_refresh_during_batch { // If we enabled coalescing and the batch-aware update... if (defer_refresh_during_batch) { processDirtyPriorities(); refresh(); } });需要特别注意的是,延迟刷新(defer)并非只依赖本开关,而是由两个开关共同决定:
coalesce_lb_rebuilds_on_batch_update:为false时,无论是否处于批量更新中,都从PriorityUpdateCb立即刷新;enable_batch_aware_update:该开关让 cluster manager 将整个批次作为一次跨线程更新,在批次结束时统一投递(来自注册在本负载均衡器之后的MemberUpdateCb)。
代码注释明确指出:只有在两个开关同时开启时,延迟刷新才是安全的(defer_refresh_during_batch = coalesce_lb_rebuilds && batch_aware_update)。因为只有投递也被延迟到批次结束时,主线程把重建也推迟到批次结束才不会被 worker 线程"插队"读到中间状态。
此外,initialize()还处理了一个边界情况:如果initialize()在批量更新过程中被调用(例如 EDSbatchUpdate -> updateHosts(P0) -> updateHosts(P1) -> onPreInitComplete -> initialize()的时序),PriorityUpdateCb可能先于initialize()触发、而MemberUpdateCb被推迟到批次结束,此时需要先processDirtyPriorities()处理已排队的优先级,确保per_priority_panic_覆盖当前全部优先级,再执行refresh()。
3.3 非线程感知负载均衡器的同类处理
这次变更不仅覆盖ThreadAwareLoadBalancerBase,同样作用于其他基于PrioritySet回调的负载均衡基类,它们全部采用"PriorityUpdateCb记录脏优先级、MemberUpdateCb统一刷新"的模式:
- load_balancer_impl.cc 中
LoadBalancerBase构造函数:开启时PriorityUpdateCb只做dirty_priorities_.insert(priority),由MemberUpdateCb调用processDirtyPriorities()一次性重算所有脏优先级(含 panic 模式重算、stashed_random_清理);关闭时则走旧的逐优先级立即重算路径; - 同文件 L458-L489 的
ZoneAwareLoadBalancerBase:开启时延迟执行 locality 权重 WRR 重建与 locality 路由结构再生成; - 同文件 L977-L992 的
EdfLoadBalancerBase(如LEAST_REQUEST):开启时PriorityUpdateCb记录脏优先级,MemberUpdateCb逐个refresh(priority)重建调度器后统一dirty_priorities_.clear(),再处理 slow start 相关逻辑。
由此可见,该特性对负载均衡器体系是"横切"的:凡是依赖PriorityUpdateCb逐优先级重建内部结构的负载均衡实现,都受益于本次合并。
四、测试验证:行为如何被证明
仓库测试从正反两个方向验证了该特性,可以作为你自行验证时的参考。
4.1 重建次数对比测试(thread-aware)
test/extensions/load_balancing_policies/ring_hash/ring_hash_lb_test.cc 中定义了一个RefreshCountingLoadBalancer,通过统计createLoadBalancer()被调用的次数来间接衡量refresh()次数(refresh()对每个优先级调用一次createLoadBalancer()):
BatchUpdateCoalescesRefreshWhenBothFlagsEnabled(两开关均开启):一次涉及 P0、P1 的批量更新后,create_count_为 2,即"一次合并刷新 × 两个优先级"——工厂只重建了一次;BatchUpdateRefreshesPerPriorityWhenCoalesceDisabled(coalesce_lb_rebuilds_on_batch_update=false):同样的批量更新产生create_count_ = 4,即"两次逐优先级刷新 × 每个刷新重建两个优先级"——冗余重建清晰可见;BatchUpdateRefreshesPerPriorityWhenBatchAwareDisabled(仅开启合并开关、关闭enable_batch_aware_update):同样不合并,证明两个开关缺一不可。
4.2 回退路径与中途初始化测试
- ring_hash_lb_test.cc L1161-L1192:
RingHashCoalesceDisabledTest.FallbackPathExercised在开关为false时验证旧的逐优先级刷新路径仍可正常工作; - ring_hash_lb_test.cc L1194-L1259:
RingHashMidBatchInitializeCrashTest模拟在批量更新过程中调用initialize()(先 P0、P1 更新再onPreInitComplete),验证开启合并后不会因新优先级出现而产生越界访问; - test/extensions/load_balancing_policies/least_request/least_request_lb_test.cc:
EdfLbCoalesceDisabledTest与EdfLbCoalesceEnabledTest分别验证LEAST_REQUEST负载均衡器在开关关闭(回退路径)与开启(合并路径,含 slow start 场景)下的行为; - test/extensions/load_balancing_policies/common/load_balancer_impl_base_test.cc:覆盖
LoadBalancerBase在两种开关取值下processDirtyPriorities()的刷新语义。
五、如何配置:查看与回退
5.1 默认状态
当前仓库中该开关已通过RUNTIME_GUARD默认开启(true),无需任何配置即生效。你可以通过 Envoy 的/runtime管理接口或日志中的 runtime 层查看实际生效值。
5.2 回退到旧行为
若在升级后遇到与负载均衡刷新行为相关的问题(例如希望恢复逐优先级立即刷新以缩小单次重建的计算窗口),可在 bootstrap 配置中通过 layered runtime 的 static layer 显式覆盖:
runtime: symlink_root: /srv/runtime/current subdirectory: envoy layers: - name: static_layer_0 static_layer: envoy: reloadable_features: coalesce_lb_rebuilds_on_batch_update: "false"同样地,如需确保批量感知投递关闭(这将使延迟刷新完全不生效),可一并设置:
envoy: reloadable_features: coalesce_lb_rebuilds_on_batch_update: "false" enable_batch_aware_update: "false"注意:如前文源码分析所述,coalesce_lb_rebuilds_on_batch_update与enable_batch_aware_update需同时开启才产生合并效果;若你出于排查目的关闭了enable_batch_aware_update,合并行为也会随之失效。
六、影响范围与注意事项
- 哪些负载均衡器受影响:所有继承
ThreadAwareLoadBalancerBase的实现(如RING_HASH、MAGLEV),以及使用LoadBalancerBase、ZoneAwareLoadBalancerBase、EdfLoadBalancerBase的实现(如LEAST_REQUEST、ROUND_ROBIN等)。判断某个负载均衡器是否走该路径,可查看其类是否继承自上述基类。 - 性能收益:在涉及多个优先级的批量主机更新(如 EDS 批量推送、健康检查状态翻转引发的大批主机变化)中,工厂重建次数从 O(优先级数) 降为 O(1),消除了批次内被中间状态覆盖的冗余计算。从源码结构看,优先级越多、批量更新越频繁,收益越明显。
- 时序语义不变:合并后的重建仍发生在 cluster manager 向 worker 线程投递更新之前(得益于回调注册顺序:负载均衡器的
MemberUpdateCb注册在 cluster manager 的投递回调之前),因此 worker 线程不会观察到"新主机 + 旧工厂"的不一致组合。 - 前提条件:合并生效依赖
enable_batch_aware_update同时开启;该开关在 runtime_features.cc 中同样是默认开启的RUNTIME_GUARD。若你通过配置覆盖关闭了它,本特性的合并效果将不生效。 - 回退与升级:本变更属于 minor behavior change(行为优化,非破坏性变更),默认开启的新路径有对应用例覆盖(含回退路径测试)。如生产环境遇到问题,可参考本文第五节配置回退,并及时反馈给社区。
参考资料(仓库内)
- 变更说明:changelogs/current/minor_behavior_changes/upstream__coalesce-lb-rebuilds-on-batch-update.rst
- 运行时开关注册:source/common/runtime/runtime_features.cc
- 线程感知负载均衡器实现:source/extensions/load_balancing_policies/common/thread_aware_lb_impl.cc
- 负载均衡基类实现:source/extensions/load_balancing_policies/common/load_balancer_impl.cc
- 相关测试:ring_hash_lb_test.cc、least_request_lb_test.cc、load_balancer_impl_base_test.cc
【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考