Envoy Load-Aware Locality 负载均衡策略:基于 ORCA 利用率余量的 locality 感知路由
【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy
导读
Envoy 在负载均衡领域引入了envoy.load_balancing_policies.load_aware_locality(Load-Aware Locality-Picking)负载均衡策略:它不再依赖静态的 locality 权重配置,而是通过 ORCA(Open Request Cost Aggregation)上报的利用率数据计算每个 locality 的可用余量(headroom),据此在多个 zone/region 之间动态分配流量,并且适用于所有 priority 级别。读完本文,你将掌握该策略的配置字段与默认值、基于 ORCA 利用率的权重计算与 EWMA 平滑原理、本地优先与远端探测机制,以及当前实现的适用边界(官方标注为 work-in-progress,不建议生产使用)。
该策略在 changelog 中登记为:load_balancing: implemented the envoy.load_balancing_policies.load_aware_locality locality-picking load balancer(见 changelogs/current/new_features/load_balancing__load-aware-locality-lb-policy.rst),属于"new features"列表中的一项新增能力。
一、策略定位:从"按配置权重"到"按利用率余量"
传统的 zone-aware 负载均衡依赖管理员为每个 locality 配置静态权重或依靠调度器下发权重;而 Load-Aware Locality 的思路是:
- 数据来源:消费 ORCA 上报的负载报告(默认走逐请求的 in-band 上报,也可开启 out-of-band 上报);
- 核心指标:以 ORCA 推导出的利用率(utilization)为基准,计算每个 locality 的"剩余容量"(headroom,即
1 - utilization); - 决策粒度:先按利用率余量为 locality 分配权重,选定 locality 之后再交给内部的 child LB 策略在 host 级别做端点选择;
- 适用范围:在所有 priority 级别(all priority levels)上都生效,而非仅作用于最高优先级。
从扩展注册信息看,该策略在 source/extensions/extensions_metadata.yaml 中以envoy.load_balancing_policies.load_aware_locality名称注册,对应的 proto 消息为envoy.extensions.load_balancing_policies.load_aware_locality.v3.LoadAwareLocality。需要特别强调的是,changelog 与本仓库源码均明确标注该扩展为work-in-progress 且不用于生产环境("The extension is work-in-progress and not intended for production use"),读者在选型时应留意这一前提。
二、配置详解:proto 字段、默认值与校验规则
策略配置定义在 api/envoy/extensions/load_balancing_policies/load_aware_locality/v3/load_aware_locality.proto,共 9 个可配置字段。下面是完整字段表:
| 字段 | 类型 | 默认值 | 校验规则 | 说明 |
|---|---|---|---|---|
endpoint_picking_policy | config.cluster.v3.LoadBalancingPolicy | 无(必填) | required | 每个 locality 内部做端点选择的 child LB 策略 |
weight_update_period | google.protobuf.Duration | 1s | 至少 100ms | 主线程上重算 ORCA 权重与 locality 利用率的周期 |
metric_names_for_computing_utilization | repeated string | 空 | — | 用于计算端点利用率的 ORCA 指标名列表 |
utilization_variance_threshold | google.protobuf.DoubleValue | 0.1 | [0, 1] | 本地利用率相对远端平均值低于该阈值时,100% 流量留在本地 |
smoothing_time_constant | google.protobuf.Duration | 5s | > 0s | locality 利用率 EWMA 平滑时间常数 |
remote_probe_fraction | google.protobuf.DoubleValue | 0.03 | [0, 1) | 为保证 ORCA 数据新鲜度而分给远端 locality 的最小流量比例 |
weight_expiration_period | google.protobuf.Duration | 3 分钟 | ≥ 0s | 单 host ORCA 样本的有效窗口,0s 表示不失效 |
enable_oob_load_report | google.protobuf.BoolValue | false | — | 是否开启端点的 out-of-band 利用率上报(默认逐请求上报) |
oob_reporting_period | google.protobuf.Duration | 10s | — | 请求服务端上报的间隔,仅当enable_oob_load_report为 true 时生效 |
以上默认值与校验规则并非仅存在于 proto 注释中——config.cc 的Factory::loadConfig逐项落实了这些约束:weight_update_period不足 100ms 时报InvalidArgumentError("weight_update_period must be at least 100ms"),smoothing_time_constant必须为正,utilization_variance_threshold必须在 [0,1] 区间,remote_probe_fraction必须在 [0,1)。对应测试 config_test.cc 中的LoadAwareLocalityConfigTest.Defaults也验证了默认值:weightUpdatePeriod() == 1000ms、utilizationVarianceThreshold() == 0.1、remoteProbeFraction() == 0.03、weightExpirationPeriod() == 180000ms。
2.1 关键字段的语义细节
metric_names_for_computing_utilization(利用率来源):对于 ORCA proto 中的 map 类型字段,字符串形如<map_field_name>.<map_key>,例如named_metrics.foo表示取 ORCAnamed_metrics字段中 key 为foo的值。利用率的取值优先级为:先取本列表指定指标中大于 0 的最大值;若都不大于 0,则回退到application_utilization;再回退到cpu_utilization。此外还存在一个运行时开关envoy.reloadable_features.orca_weight_manager_use_named_metrics_first(见 source/common/runtime/runtime_features.cc 中的RUNTIME_GUARD声明):禁用该开关可恢复旧优先级顺序,即优先使用application_utilization而非 named metrics。
utilization_variance_threshold(本地偏好阈值):当本地 locality 的利用率"至多"比远端平均利用率高该阈值时,100% 的流量路由到本地,从而避免在负载基本均衡时产生不必要的跨 zone 流量。该判断是单向的:若本地 zone 负载低于远端,则始终全本地路由。实现位于computeSourceWeights:当utilizations[0] <= target_util + utilization_variance_threshold_时置local_preferred并将全部权重收敛到本地。
smoothing_time_constant(EWMA 平滑):每个 tick 的平滑因子alpha = 1 - exp(-weight_update_period / smoothing_time_constant)。由于 alpha 由 tick 周期与时间常数共同推导,无论配置多快的更新周期,settling 时间保持一致:默认 5s 常数下约 95% 在约 15s 内收敛。更大的值权重更稳定、反应更慢;更小的值反应更快。该推导在 config.cc 中实现:ewma_alpha = 1.0 - std::exp(-weight_update_period / smoothing_time_constant)。
remote_probe_fraction(远端探测流量):当本地偏好判断会导致 100% 流量留在本地时,为保证 ORCA 数据不陈旧,需要保留一小部分流量打到远端 locality。该比例是全局值,会按 host 数量比例拆分到所有远端 locality;注意"探针比例是全局值,分摊到所有远端 locality"——在远端 locality 数量非常多且总请求率很低时,单个 host 的采样间隔可能超过weight_expiration_period(proto 注释提到架构总览中给出了扩展性矩阵)。设为 0 可关闭,但仅当 ORCA 报告以 out-of-band 方式到达、或必须严格避免跨 zone 流量时才安全。
weight_expiration_period(样本有效期):host 在该时长内没有上报负载指标,则被排除出其 locality 的利用率聚合。locality 的 EWMA 状态会在剩余上报 host 上继续正常演进。若某个 locality 的全部 host 都过期,该 locality 回退到按 host 数量成比例的权重(与所有 locality 过载时的路径相同)。设为 0s 关闭过期机制。
三、完整的 YAML 配置示例
下面是在 cluster 上启用该策略的最小可运行配置(children 使用 round_robin 作为 locality 内部端点选择器):
load_balancing_policy: policies: - typed_extension_config: name: envoy.load_balancing_policies.load_aware_locality typed_config: "@type": type.googleapis.com/envoy.extensions.load_balancing_policies.load_aware_locality.v3.LoadAwareLocality endpoint_picking_policy: policies: - typed_extension_config: name: envoy.load_balancing_policies.round_robin typed_config: "@type": type.googleapis.com/envoy.extensions.load_balancing_policies.round_robin.v3.RoundRobin weight_update_period: 1s utilization_variance_threshold: 0.1 smoothing_time_constant: 5s remote_probe_fraction: 0.03 weight_expiration_period: 180s metric_names_for_computing_utilization: - named_metrics.foo # enable_oob_load_report: true # oob_reporting_period: 10s在 integration_test.cc 的集成测试中可以看到该策略的完整装配方式:测试将 node 的 locality 设置为region=test-region, zone=zone-a,在load_assignment下同时配置本地 zone(zone-a)与远端 zone(zone-b)的 locality 分组,再通过load_balancing_policy挂载上述策略,从而验证跨 zone 场景下的路由行为。这也提示了一个部署前提:该策略依赖端点配置中 locality 分组信息(load_assignment.endpoints[].locality),本地 locality 由 Envoy 自身所在 zone 判定。
四、实现原理:主线程算权重,Worker 线程选端点
从源码结构看,该扩展的实现是典型的主线程/工作线程分工模型,核心类位于 load_aware_locality_lb.h 与 load_aware_locality_lb.cc:
- 主线程:
LoadAwareLocalityLoadBalancer(实现ThreadAwareLoadBalancer)通过定时器周期性地执行computeLocalityRoutingWeights(),把所有 priority、所有 SelectionSource(Healthy / Degraded / AllHosts)下的 locality 权重算好后,以RoutingWeightsSnapshot快照形式通过 TLS(ThreadLocalShim)发布到各 worker; - Worker 线程:
WorkerLocalLb(继承LoadBalancerBase)维护每个 locality 的 child LB(PerLocalityState),每次选路时先做 live 的 priority/健康度选择(resolvePrioritySource),再按发布的权重快照选择 locality(chooseLocality),最后把请求委托给该 locality 的 child LB 完成端点选择(chooseHost)。
4.1 权重计算:host 数 × (1 - utilization)
computeSourceWeights是权重计算的核心(load_aware_locality_lb.cc):
- 对每个 locality,遍历其健康/降级/全部 host,读取每个 host 的 ORCA 利用率,过滤掉从未上报(
kNeverReported)或已超过weight_expiration_period的过期 host,求平均得到avg_utils[i]; - 用 EWMA 平滑:
smoothed = alpha * avg_utils[i] + (1 - alpha) * prev,平滑后的利用率存入LocalityEwmaMap(以 locality 身份为 key,避免 host 增删导致的下标错位); - 计算基础权重:
weight = host_count * max(0.0, 1.0 - utilization)——即利用率余量(headroom)越大、host 越多,权重越高;无有效样本(stale)的 locality 直接退化为host_count; - 三种退化/修正路径:
- 全过载(all_overloaded):若总基础权重为 0 且存在 host,回退为按 host 数量成比例分配;
- 本地偏好(local_preferred):本地利用率 ≤ 远端加权平均利用率 + 阈值时,全部权重收敛到本地(
all_local); - 远端探测(probe_active):若远端权重占比低于
remote_probe_fraction,从本地权重中切出一部分,按远端 host 数量比例重新分配。
4.2 ORCA 上报数据的消费与新鲜度
每个 host 的负载数据存放在LocalityLbHostData(实现HostLbPolicyData)中:worker 线程调用onOrcaLoadReport写入利用率,主线程读取。跨线程写入通过std::atomic<double>与std::atomic<MonotonicTime>完成,代码中有static_assert保证两个原子类型都是无锁的(lock-free)。值得注意的是:无利用率信号(util <= 0.0)的报告不会刷新 freshness 时间戳,从而让静默 host 自然过期而不是"钉住"空闲状态。
底层 ORCA 处理复用能力来自 source/extensions/load_balancing_policies/common/orca_weight_manager.h:OrcaLoadReportHandler::getUtilizationFromOrcaReport()负责从xds.data.orca.v3.OrcaLoadReport中按metric_names_for_computing_utilization提取利用率;OrcaWeightManager原本从 ClientSideWeightedRoundRobin 中抽取、供多个 LB 策略复用。本策略的 host 数据仅存利用率与时间戳(不做 QPS/EPS 加权),这是它与 client_side_weighted_round_robin 的差异点。
4.3 每次选路:一次随机、两步决策
Worker 端每次选路只消耗一次随机数(resolvePrioritySource中random(peeking)),priority/健康度选择与 locality 选择共享该随机数:locality 目标通过splitMix64(hash)二次派生(黄金比例递增 + 收尾混合),使两个决策相互独立。权重快照在发布时即被转换为按 locality 下标的前缀和数组(refreshLocalityWeights),选路时用std::upper_bound二分查找完成,避免逐次哈希 locality 身份;快照与拓扑不一致时(如 child LB 因 host 变更被拆除),pickLocalityLb会从下一个下标开始环绕扫描可用的 child LB,避免并发 drain 全部压到同一个 locality。
4.4 可观测性:专用计数器
主线程每次重算都会更新以下计数器(定义于 load_aware_locality_lb.h),统计名前缀为load_aware_locality:
| 计数器 | 语义 |
|---|---|
recompute_total | 权重重算总次数 |
all_overloaded_total | 出现全过载回退的 tick 数 |
local_preferred_total | 触发本地偏好(全本地路由)的 tick 数 |
probe_active_total | 远端探测流量生效的 tick 数 |
spill_active_total | 本地负载过高、向外溢出的 tick 数 |
stale_locality_total | 累计的陈旧 locality 计数 |
此外 Worker 端复用标准 zone 路由统计(lb_zone_routing_cross_zone、lb_zone_routing_all_directly、lb_zone_routing_sampled)来区分跨 zone、全直达与被采样的路由(见recordZoneRoutingStats)。
五、边界条件与适用前提
- 官方状态:该扩展为 work-in-progress,changelog 与代码注释均明确不用于生产环境,评估与选型时请以这一状态为准;
- 配置校验细节:
endpoint_picking_policy是必填项,且不允许嵌套自身(load_aware_locality cannot be its own endpoint_picking_policy,避免统计重复注册与 metric 丢失);若无法实例化 child 策略,集群初始化会失败(Unsupported endpoint picking policy for load_aware_locality: ...),从而不会进入 worker 阶段(见 config.cc 与 load_aware_locality_lb.cc); - 数据新鲜度与探针比例的权衡:默认 3% 的远端探针流量是全局值,跨 zone 流量被严格禁止的部署需要显式置 0(仅当 ORCA 走 out-of-band 上报时安全);远端 locality 数量多而请求量低时,需参照
weight_expiration_period与采样矩阵调整参数; - 依赖 ORCA 上报链路:默认逐请求(in-band)上报,要求端点侧能够随 RPC 响应携带 ORCA 负载报告;如需降低 per-request 开销,可开启
enable_oob_load_report并配合oob_reporting_period(服务端实际上报频率可能低于请求值)。
六、结语
envoy.load_balancing_policies.load_aware_locality是 Envoy 在 locality 级负载均衡上的一次新尝试:它把 ORCA 利用率数据、EWMA 平滑、本地偏好阈值与远端探测机制组合成一套"按剩余容量路由"的 locality 选择策略,并在所有 priority 级别生效。其主线程计算/Worker 执行的架构、基于前缀和与二分查找的选路路径、以及完善的统计埋点,为后续在生产环境打磨提供了清晰的实现基础。读者可结合 api 侧 proto 定义、主实现 以及 config 测试、集成测试 继续深入验证其行为。
【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考