资讯动态

负载均衡选错:轮询在长连接下的偏斜问题

发布时间:2026/10/16 3:08:59 来源:尧图企业网站定制
我不会起名字322· 后端 / 算法 / 数据库 技术栈 力扣 Hot100 Go 项目 Redis MySQL文章目录负载均衡选错轮询在长连接下的偏斜问题一、现象明明配了轮询流量却集中在某一台二、两种负载均衡的区别具体会偏成什么样三、为什么连接级是默认四、解法 1用请求级负载均衡五、解法 2用 least_request最少请求六、解法 3定期重建连接有争议的兜底七、还有一个坑K8s Service 的负载均衡八、怎么验证流量是不是偏的九、带走物负载均衡选错轮询在长连接下的偏斜问题一、现象明明配了轮询流量却集中在某一台典型场景服务有 4 个实例配了round_robin但监控显示其中 1~2 个实例的 CPU 是其他的 2~3 倍扩容之后没有好转新的实例几乎收不到流量重启调用方之后分布变均匀跑一段时间又偏了。根因只有一个你在长连接上做每条连接轮询而不是每个请求轮询。二、两种负载均衡的区别这是理解问题的关键。gRPC以及很多 RPC 框架区分两种 LB连接级 LB请求级 LB决策时机建连时决定连哪个后端每次请求决定发哪个后端实现位置客户端建连接池时客户端每次调用时连接数每个后端一条连接每个后端一条连接轮询含义“新建的第 N 条连接连第 N 个后端”“第 N 个请求发给第 N 个后端”长连接 连接级 LB 灾难连接一旦建立就长期复用轮询只在建连那一刻生效之后所有请求都走那条已经选定的连接。具体会偏成什么样假设调用方有 3 个实例被调用方有 4 个实例调用方 A 建了 2 条连接 → 都连到了 后端 1 调用方 B 建了 2 条连接 → 都连到了 后端 2 调用方 C 建了 2 条连接 → 都连到了 后端 3 后端 4一个都没连上结果后端 4 完全空闲后端 1/2/3 各承担 1/3。这还算好的情况。更常见的是某个调用方连了 5 条连接全落到一个后端上。HTTP/2 让这个问题更严重gRPC 用 HTTP/2多个请求可以在同一条连接上多路复用。所以一条连接就能承载全部流量——“连接级轮询退化成了随机选一个后端然后一直用”。三、为什么连接级是默认这不是设计缺陷是历史原因连接级 LB 是传输层做的在 TCP 建连时选一个 IP。它不需要理解应用协议性能好、实现简单。请求级 LB 需要框架在每次调用时做决策成本高一点要维护每个后端的连接、要统计、要选。问题在于HTTP/1.1 的时代连接级 LB 勉强够用因为一条连接同时只能处理一个请求所以会建很多连接分布还算均匀。而HTTP/2 的多路复用让一条连接承载所有请求成为常态连接级 LB 就彻底失效了。四、解法 1用请求级负载均衡gRPC 里显式指定conn,err:grpc.Dial(dns:///order-service.default.svc.cluster.local:8080,grpc.WithDefaultServiceConfig({loadBalancingPolicy:round_robin}),)注意dns:///这个 schemeorder-service:8080 → 默认 pick_first连接级 dns:///order-service:8080 → 解析出多个 IP配合 round_robin 做请求级轮询只写round_robin但地址是passthrough模式是没有效果的——因为 passthrough 只解析出一个地址DNS 返回的那个或者把整个名字当成一个后端。完整的正确写法conn,err:grpc.Dial(dns:///order-service.default.svc.cluster.local:8080,grpc.WithTransportCredentials(insecure.NewCredentials()),grpc.WithDefaultServiceConfig({ loadBalancingPolicy: round_robin, healthCheckConfig: {serviceName: } }),)加上 health check 更重要没有健康检查时gRPC 会往已经下线的后端继续发请求直到连接失败才发现。五、解法 2用 least_request最少请求round_robin还有一个问题它假设每个请求代价相同。如果请求耗时差异很大有的 10ms有的 2 秒轮询会让分到慢请求的实例堆积更多在途请求。least_request会选当前在途请求最少的后端grpc.WithDefaultServiceConfig({loadBalancingPolicy:least_request})对比策略适用不适用pick_first单后端 / 只有一个实例多实例默认就是这个很容易踩坑round_robin请求耗时均匀耗时差异大least_request耗时差异大需要额外的统计开销我的建议默认就用least_request它的开销很小但对长尾请求的容忍度高得多。六、解法 3定期重建连接有争议的兜底有些框架没有请求级 LB只能在连接层面缓解——定期断掉连接重建让轮询重新发生。// 给连接设一个最大存活时间到期强制重建config:sarama.NewConfig()// Kafka 的 Consumer 或者 HTTP transporttr:http.Transport{MaxIdleConnsPerHost:100,IdleConnTimeout:60*time.Second,// 空闲 60s 就关掉重建}这是治标不是治本而且有副作用频繁重建连接会带来 DNS TCP TLS 的开销重建瞬间可能出现所有连接同时失效如果所有调用方的重建周期一致会造成周期性抖动要加随机抖动。只有在框架不支持请求级 LB 时才用它。七、还有一个坑K8s Service 的负载均衡K8s 的 ServiceClusterIP做的是连接级负载均衡不是请求级。这意味着Pod A 通过 ClusterIP 访问 Service → iptables/ipvs 在**建连时**选一个后端 Pod → 这条连接之后的所有请求都发给同一个后端 Pod所以 gRPC 通过 K8s Service 访问流量必然是偏的而且扩容 Service 后面的 Pod 也不会改善——因为长连接早就建好了。四种解法方案做法代价客户端服务发现不用 Service客户端直接拿 Endpoint 列表做请求级 LB需要服务发现组件Service Mesh用 Istio/Linkerdsidecar 做请求级 LB架构复杂度和资源开销headless ServiceclusterIP: NoneDNS 返回所有 Pod IP客户端自己做 LB客户端要支持定期断连缩短IdleConnTimeout治标有开销headless Service gRPC 的dns:///是最轻量的组合apiVersion:v1kind:Servicespec:clusterIP:None# headlessselector:app:order// 客户端拿到所有 Pod IP自己做请求级轮询conn,_:grpc.Dial(dns:///order.default.svc.cluster.local:8080,grpc.WithDefaultServiceConfig({loadBalancingPolicy:round_robin}))八、怎么验证流量是不是偏的不要只看调用方的指标要看每个后端实例收到的请求数# 按 Pod 聚合 gRPC 请求数看分布 sum by (pod) (rate(grpc_server_started_total[5m]))健康的分布应该是各实例接近平均值。如果最大值 / 最小值 1.5就是明显偏斜。也可以在客户端 dump 一下连接池状态很多框架支持curlhttp://localhost:8080/debug/lb# 看每个后端分配了多少请求九、带走物检查清单1. 负载均衡策略是不是默认的 pick_first └─ 是 → 改成 round_robin 或 least_request 2. gRPC 的地址 scheme 对不对 └─ 必须是 dns:///passthrough 模式下 round_robin 无效 3. 是不是通过 K8s Service 访问 gRPC └─ 是 → Service 只做连接级 LB要换 headless Service 或上 Mesh 4. 有没有开健康检查 └─ 没有 → 加上否则会往已下线的实例发请求 5. 验证sum by (pod) (rate(grpc_server_started_total[5m])) └─ 最大值/最小值 1.5 就是偏斜一句话结论长连接上的轮询如果不做在请求级就等于没做——建连时选一次之后所有请求都走那条连接HTTP/2 的多路复用会把这个问题放大到一个后端扛全部。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑