尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Envoy 上游连接优化:Happy Eyeballs 地址列表排序改为“创建时一次性完成“

Envoy 上游连接优化:Happy Eyeballs 地址列表排序改为“创建时一次性完成“ Envoy 上游连接优化Happy Eyeballs 地址列表排序改为创建时一次性完成【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy导读本文解读 Envoy 上游upstream连接路径中的一项重要行为变更Happy Eyeballs 对多地址主机地址列表的排序从每次上游连接尝试时都重新排序改为地址列表创建或刷新时只排序一次。这一改动消除了连接热路径上的重复计算同时严格保证了连接尝试的顺序与原先完全一致。读完本文你将理解 Envoy 中 Happy Eyeballs 的完整实现链路从 DNS 解析结果到连接池建连、排序算法依据的 RFC 8305 规则、happy_eyeballs_config配置项的语义以及本次改动在静态 Host 与动态 LogicalHost 两种场景下的落地方式。变更背景Envoy 中的 Happy EyeballsHappy Eyeballs 算法RFC 6555 / RFC 8305解决的是一个经典问题当一个主机名同时解析出 IPv6 与 IPv4 多个地址时如何避免因首选地址族不可达而白白等待例如 IPv6 路由黑洞导致 TCP 握手超时同时又不浪费两端能力。其核心思想是对多个地址族并行发起连接尝试谁先成功就用谁从而把连接延迟降低到最快可用地址族的水平。在 Envoy 中该能力由 happy_eyeballs_connection_impl.cc 中的HappyEyeballsConnectionProvider与HappyEyeballsConnectionImpl实现HappyEyeballsConnectionProvider::sortAddresses()是一个静态方法负责按 RFC 8305 第 4 节的要求把地址列表按地址族交错重排HappyEyeballsConnectionImpl是一个透明的ClientConnection它拿着排好序的地址列表按顺序逐个发起连接尝试并使用第一个成功建立连接的地址。实现细节决定了排序结果被谁消费HappyEyeballsConnectionImpl构造函数接收的地址列表必须已经排好序。因此排序发生在哪里、多久发生一次直接决定了建连热路径的开销。本次变更排序时机从每次连接前移到创建/刷新时变更条目位于 happy_eyeballs__sort-address-list-once.rst原文核心表述为The happy eyeballs sorting of a multi-address hosts address list now happens once when the address list is created or refreshed, instead of on every upstream connection attempt. The order in which connection attempts are made is unchanged.即变更前每次创建上游连接createConnection时都要对地址列表重新执行一次 Happy Eyeballs 排序变更后排序只在地址列表创建或刷新如 EDS 动态更新时执行一次结果被缓存复用不变实际发起连接尝试的地址顺序与之前完全一致行为语义无任何变化。这是一个典型的消除重复计算式优化排序结果只取决于地址列表本身和happy_eyeballs_config配置两者在主机生命周期内不变或仅在刷新时变化因此每次建连都重新排序纯属浪费。源码剖析排序如何从热路径上摘除排序入口makeSortedAddressListOrNull排序的核心逻辑收敛在 upstream_impl.cc 的HostDescriptionImplBase::makeSortedAddressListOrNull()HostDescription::SharedConstAddressVector HostDescriptionImplBase::makeSortedAddressListOrNull(const ClusterInfo cluster, const AddressVector address_list) { if (address_list.size() 1) { return {}; } const envoy::config::cluster::v3::UpstreamConnectionOptions::HappyEyeballsConfig happy_eyeballs_config cluster.happyEyeballsConfig().has_value() ? *cluster.happyEyeballsConfig() : defaultHappyEyeballsConfig(); return std::make_sharedAddressVector( Network::HappyEyeballsConnectionProvider::sortAddresses(address_list, happy_eyeballs_config)); }两点值得注意少于 2 个地址直接跳过当地址列表只有 0 或 1 个地址时返回空指针Happy Eyeballs 不适用建连走普通路径对应 upstream_impl.h 中happy eyeballs does not apply的注释配置缺省自动补齐如果集群没有配置happy_eyeballs_config则使用defaultHappyEyeballsConfig()——该默认配置位于 upstream_impl.cc即first_address_family_version DEFAULT跟随地址列表首地址的地址族、first_address_family_count 1。结果缓存sorted_address_list_or_null_排序结果被存入主机对象的成员变量sorted_address_list_or_null_。在静态主机HostDescriptionImpl中upstream_impl.h该字段的注释明确写道Happy eyeballs sorted copy of the address list, or nullptr if the host does not have multiple addresses.Set at construction and never changed; read byHostImpl::sortedAddressListOrNull().对应构造函数upstream_impl.cc在初始化列表中一次性完成排序sorted_address_list_or_null_(makeSortedAddressListOrNull(*cluster, address_list)),由于静态主机的地址列表在构造后不可变排序结果天然只需计算一次。消费端createConnection 直接复用建连入口 upstream_impl.cc 在构造连接时不再执行任何排序而是直接读取缓存} else if (sorted_address_list ! nullptr sorted_address_list-size() 1) { ENVOY_LOG(debug, Upstream using happy eyeballs config.); connection std::make_uniqueNetwork::HappyEyeballsConnectionImpl( dispatcher, sorted_address_list, source_address_selector, socket_factory, transport_socket_options, host, options); }即只要主机持有多于 1 个地址的已排序列表就创建HappyEyeballsConnectionImpl把已排好序的列表交给它按序尝试。这正是 happy_eyeballs_connection_impl.h 中HappyEyeballsConnectionProvider类注释所要求的契约——The address list passed to the constructor must already be sorted withsortAddresses(); the host computes this once when its address list is created or refreshed rather than on every connection attempt.动态场景LogicalHost 的地址刷新同步重排对于 EDS 等动态发现场景主机地址会随LbEndpoint更新而刷新此时使用的是 logical_host.cc 中的LogicalHost。其构造时同样调用一次makeSortedAddressListOrNull()第 42 行而在地址刷新入口setNewAddresses()logical_host.cc中每次刷新都会重新计算排序结果并原子性地与原始列表一同更新SharedConstAddressVector sorted_address_list makeSortedAddressListOrNull(cluster(), address_list); { absl::MutexLock lock(address_lock_); address_ address; address_list_or_null_ std::move(shared_address_list); sorted_address_list_or_null_ std::move(sorted_address_list); health_check_address_ std::move(health_check_address); }对应 logical_host.h 中两个关键注释也印证了设计意图sortedAddressListOrNull()被设计为publicso that tests can verify the sorted list is kept in sync with the raw list across address refreshes便于测试验证刷新后排序列表与原始列表保持一致成员字段sorted_address_list_or_null_是 Happy eyeballs sorted copy ofaddress_list_or_null_, updated together with it so that the raw and sorted lists stay consistent。因此无论静态还是动态主机创建或刷新时排序一次都得到了严格保证——这正是本次变更的完整语义。排序算法RFC 8305 第 4 节的实现sortAddresses()的完整实现位于 happy_eyeballs_connection_impl.cc其注释直接引用了https://datatracker.ietf.org/doc/html/rfc8305#section-4。算法分三步第一步确定首选地址族preferred family。默认取地址列表第一个地址的地址族DEFAULT若配置了first_address_family_version则按枚举值覆盖为 V4 / V6 / PIPE / INTERNAL。第二步按地址族分组bucket。遍历输入列表用AddressFamily{type, version}作为键分组同时维护一个family_order序列——首选地址族被插入到序列最前面其余按首次出现的顺序排列。地址族不止 IPv4/IPv6还覆盖了 Pipe 与 EnvoyInternal 类型对应 happy_eyeballs_connection_impl.cc 的getFamily()以及 upstream_impl.h 中happy eyeballs does not apply仅针对少于 2 个地址的约束。第三步交错输出。采用轮转round-robin方式逐族取地址首选地址族每次取first_address_family_count个其余地址族每次取 1 个直到全部地址取完。源码注释给出了直观示例——若首选族为 v6、count 为 3输出形如[3*v6, 1*v4, 3*v6, 1*v4, ...]前提是输入中存在足够多的 v6 地址族内地址不足时自然缩短该轮取值。配置参数happy_eyeballs_config 全解排序行为由集群级配置upstream_connection_options.happy_eyeballs_config控制其 proto 定义位于 cluster.protomessage UpstreamConnectionOptions { enum FirstAddressFamilyVersion { DEFAULT 0; // Use the first address family encountered in the address list. V4 1; V6 2; PIPE 3; INTERNAL 4; } message HappyEyeballsConfig { // Specify the IP address family to attempt connection first in happy // eyeballs algorithm according to RFC8305#section-4. FirstAddressFamilyVersion first_address_family_version 1; // Specify the number of addresses of the first_address_family_version being // attempted for connection before the other address family. google.protobuf.UInt32Value first_address_family_count 2 [(validate.rules).uint32 {gte: 1}]; } HappyEyeballsConfig happy_eyeballs_config 3; // ... 其他字段省略 }参数语义与默认值总结参数类型默认值说明first_address_family_version枚举DEFAULT指定首先尝试连接的地址族DEFAULT表示以地址列表首地址的地址族为准。取值含 V4、V6、PIPE、INTERNALfirst_address_family_countUInt32Value1首选地址族在切换到其他地址族前连续尝试的地址数量校验规则为 1默认值同样体现在源码两处一是defaultHappyEyeballsConfig()upstream_impl.cc二是排序实现中通过PROTOBUF_GET_WRAPPED_OR_DEFAULT(..., 1)对first_address_family_count的兜底happy_eyeballs_connection_impl.cc。对应的 YAML 配置写法为clusters: - name: example_cluster connect_timeout: 5s type: STRICT_DNS lb_policy: ROUND_ROBIN load_assignment: cluster_name: example_cluster endpoints: - lb_endpoints: - endpoint: address: socket_address: address: dualstack.example.com port_value: 443 upstream_connection_options: happy_eyeballs_config: first_address_family_version: V6 first_address_family_count: 2值得说明的是本次排序时机变更不改变任何配置语义排序算法、参数含义、默认值、输出顺序均保持原样只是计算时点被提前并缓存。测试验证排序行为与默认值兜底排序算法的行为由单元测试完整锁定见 happy_eyeballs_connection_provider_test.ccSortAddresses第 15-55 行验证默认配置下纯 v4 / 纯 v6 列表保持不变[v6, v6, v4, v4]被交错为[v6, v4, v6, v4]混合列表按族轮转输出SortAddressesWithFirstAddressFamilyCount第 57-117 行验证V4 count2时[v6, v4, v6, v4]变为[v4, v4, v6, v6]v4 族每次取 2 个同时覆盖缺省分支——缺first_address_family_version时回落为DEFAULT语义、缺first_address_family_count时回落为 1SortAddressesWithNonIpFamilies第 119 行起验证非 IP 地址族Pipe 等同样参与交错排序。此外logical_host.h 特意将sortedAddressListOrNull()从 protected 提升为 public目的就是让测试可以跨地址刷新断言排序列表与原始列表始终同步——这直接为本次刷新时重新排序、连接时不再排序的新行为提供了可验证的测试入口。影响评估与总结从源码结构可以推断本次变更带来的收益集中在连接热路径的算力削减消除重复计算此前每次createConnection都要执行一遍地址族分组与交错现在排序结果以shared_ptr形式缓存于sorted_address_list_or_null_建连时零排序开销列表 2 个地址的普通路径更是不受影响语义完全等价由于排序纯函数依赖于地址列表配置二元组而两者在两次连接尝试之间保持不变缓存结果与逐次重算结果严格一致——连接尝试顺序不变这正是变更条目中特别声明unchanged的原因动态刷新正确性LogicalHost::setNewAddresses()在地址刷新时同步重排保证 EDS 更新后缓存始终新鲜不引入过期地址尝试。对于运行着大量多地址如 STRICT_DNS 双栈上游集群的 Envoy 而言该改动把排序成本从每个连接尝试一次降为每个地址列表生命周期一次属于典型的低风险高收益优化。理解这一变更也有助于排查与上游连接顺序相关的疑难问题当看到日志中 Upstream using happy eyeballs config. 时可以明确该排序列表是在主机创建/刷新时生成的而非连接时生成。【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表