一次搞懂 Envoy Composite Cluster:重试第几次,流量就进第几号集群
【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy
Envoy 的envoy.clusters.composite集群类型把每一次重试尝试路由到固定的子集群:attempt 1 命中第 1 个,attempt 2 命中第 2 个,超出数量即请求失败。它是需要“首选 → 降级 → 兜底”确定性路由的代理开发者的现成方案;如果你的诉求是按健康比例分流,它不适用。
一句话结论:attempt N 恒命中第 N-1 个子集群,健康度不参与决策
Composite Cluster 用一条映射式替换了 Aggregate Cluster 的“按健康比例分流”:cluster_index = attempt_count - 1。它解决的痛点是:同一上游存在多档(多提供商、多成本档位)时,需要把“每次尝试走哪”表达成一条固定降级路径,而健康比例分流表达不了次序,结果还会随健康状态漂移。
| 决策维度 | Aggregate Cluster | Composite Cluster |
|---|---|---|
| 决定子集群的输入 | 各子集群健康占比 | 重试尝试编号 |
| 设计目标 | 按健康做切换与分流 | 固定降级次序(retry progression) |
| 尝试超出容量时 | 无溢出概念,继续按健康分流 | 直接 no host available,请求失败 |
| 结果可复现性 | 随健康状态波动 | 同配置同主机可用性下恒定 |
三个值得用的场景:重试演进、提供商切换、成本分档
三类场景的共同特征:按“尝试次序”而非“健康占比”决定流量走向。
- 重试演进:primary → secondary → tertiary,随尝试次数逐级降级;
- AI Gateway 多提供商切换:初始请求进首选模型/提供商,重试切备用提供商;
- 成本分档:先试昂贵的高性能服务,失败后回落便宜的替代服务。
最小配置:一个 ClusterConfig 定义 Composite Cluster
Composite Cluster 本体只有一个字段——clusters列表,最小配置共 12 行 YAML:
name: composite_cluster connect_timeout: 0.25s lb_policy: CLUSTER_PROVIDED cluster_type: name: envoy.clusters.composite typed_config: "@type": type.googleapis.com/envoy.extensions.clusters.composite.v3.ClusterConfig clusters: - name: primary_cluster - name: secondary_cluster - name: fallback_cluster逐字段解读:
lb_policy: CLUSTER_PROVIDED必填:composite 自身不持有 host,也不持有负载均衡算法,“集群提供”的正是它注册的CompositeClusterLoadBalancer;clusters每条只有一个name字段:被引用的集群必须在配置其他位置独立定义,endpoint、负载均衡算法、健康检查、outlier detection 全在子集群侧;- 列表顺序即语义:第 1 个 = 初始请求,第 2 个 = 第 1 次重试,依此类推;
- cluster.proto 的 validate 规则(
min_items: 1、min_len: 1)在配置加载阶段就拒绝空列表与空名,官方语义另见 composite_cluster.rst。
源码时间线:取尝试次数 → 减一映射 → 委托子集群
选择逻辑全部运行在 worker 线程本地的负载均衡器中:CompositeClusterLoadBalancer由工厂在每个 worker 线程独立构造(cluster.h),子集群增删通过构造时注册的更新回调同步到各线程,选择路径无跨线程开销。
第 1 步,取尝试次数。getAttemptCount()(cluster.cc)从请求的 StreamInfo 读取路由写入的 1 基尝试编号;上下文为空或取值缺失 → 返回 0:
auto* stream_info = context->requestStreamInfo(); if (stream_info != nullptr && stream_info->attemptCount().has_value()) { return stream_info->attemptCount().value(); } return 0;0 是哨兵值而非真实尝试数,下一步按异常处理。
第 2 步,1 基转 0 基。mapAttemptToClusterIndex()(cluster.cc):
if (attempt_count == 0) { ENVOY_LOG(warn, "invalid attempt count 0 in composite cluster '{}'", parent_info_->name()); return std::nullopt; } const size_t cluster_index = attempt_count - 1; if (cluster_index < clusters_->size()) { return cluster_index; } return std::nullopt;attempt 0 与下标越界都返回nullopt,chooseHost()随即返回无 host 响应,请求以“无可用主机”失败。
第 3 步,委托子集群。selectHostWithFailover()先用CompositeLoadBalancerContext包装原始上下文(全部方法透传,另记录selected_cluster_index供调试,见 lb_context.h),再调用子集群自身的chooseHost();主机级选择由子集群自己的负载均衡算法完成。peekAnotherHost、selectExistingConnection走同样的“先映射下标、再委托”路径。
⚠️ 三个坑:无主机回退、越界即失败、回退不移动后续映射
坑 1,无主机回退发生在同一次尝试内。映射到的子集群无可用主机(DNS 解析结果为空、主机被 outlier detection 全部逐出)→ 同一尝试内向列表后序集群顺延;后序全空 → 以no_healthy_upstream失败:
for (size_t cluster_index = start_index; cluster_index < clusters_->size(); ++cluster_index) { auto* cluster = getClusterByIndex(cluster_index); if (cluster != nullptr) { CompositeLoadBalancerContext composite_context(context, cluster_index); response = cluster->loadBalancer().chooseHost(&composite_context); if (response.host != nullptr || response.cancelable != nullptr) { return response; } } if (!skip_clusters_without_hosts) { break; } }子集群返回异步主机选择进行中(cancelable非空)→ 循环立即终止,后续选择归异步流程所有,其余集群不再尝试。
坑 2,回退受 runtime 开关保护。envoy.reloadable_features.composite_cluster_skip_clusters_without_hosts默认开启;置 false → 映射到的子集群无主机时立即失败、不回退。该开关即“误报 503 no_healthy_upstream”修复(变更说明)的回退入口。
坑 3,回退不移动后续尝试的映射。[primary, secondary, fallback]且 primary 无主机时:attempt 1 回退到 secondary,但 attempt 2 仍映射到 secondary 而非 fallback——映射只由尝试次数驱动。设计重试规模时必须计入这一点。
配套要求:重试次数 = 子集群数,验证看三个入口
Composite 集群只有在路由配置了配套重试策略时才成立,且次数必须对齐子集群数量:
retry_policy: retry_on: "5xx,gateway-error,connect-failure,refused-stream" num_retries: 2 # 共 3 次尝试,覆盖 3 个子集群num_retries: 2→ 最多 3 次尝试:配小了尾部子集群被浪费,配大了溢出尝试直接失败。
验证入口有三处:
- 单测 cluster_test.cc:覆盖空上下文、缺 StreamInfo、越界尝试数、下标映射、failover 与异步选择边界,以及开关关闭后的回退验证;
- 集成测试 cluster_integration_test.cc:3 个 fake upstream + 1 个 composite 集群,构造“endpoint 列表为空”验证同一尝试内 failover,并用
include_request_attempt_count观测每次尝试命中的集群; - 配置校验:cluster.proto 的 validate 规则在加载期拒绝空列表、空名。
✅ 速查:五条配置纪律
lb_policy固定CLUSTER_PROVIDED,composite 自身不持 host 与负载均衡算法;- 列表顺序 = 尝试次序,且每个被引用子集群须在配置中独立定义;
num_retries + 1与子集群数量对齐,retry_on按降级目标配置;- 越界尝试直接失败,不存在回绕到首个集群;
- 回退只影响当前尝试、不移动后续映射,按档降级时应在列表末尾预留兜底子集群。
【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考