Skip to content

面向微服务内部调用的负载均衡架构选择

概览

系统比较网关负载均衡与客户端负载均衡,给出微服务内部调用场景下的默认架构选择、算法建议与落地实践。

摘要

在现代微服务系统中,负载均衡已经不只是“把请求平均分给多个实例”的网络问题,而是服务发现、故障隔离、流量治理、弹性伸缩、可观测性和调用链稳定性的综合问题。本文的核心判断是:对内部服务间调用,也就是东西向流量,客户端负载均衡或 sidecar 代理负载均衡应当作为默认选择;对外部入口,也就是南北向流量,仍然保留网关、Ingress、ALB、NLB、API Gateway 等集中式负载均衡。 把所有内部调用都压到网关上,是一种过时且危险的集中式设计,容易制造额外跳数、瓶颈和单点爆炸半径。

Kubernetes 官方文档指出,Service 的目标是在工作负载可能动态变化、Pod 可能随时创建销毁的情况下,将一组后端以网络服务的形式暴露出来;如果应用能够使用 Kubernetes API 做云原生服务发现,可以查询 EndpointSlice,而非云原生应用则可在应用与后端 Pod 之间放置网络端口或负载均衡器。这个事实直接说明:现代负载均衡的本质已经从“固定后端列表”变成了“动态端点发现 + 策略化路由”。(Kubernetes)

关键词: 客户端负载均衡、网关负载均衡、服务发现、gRPC、Envoy、Istio、Kubernetes、Round Robin、Least Request、Consistent Hash


1. 负载均衡策略的背景

负载均衡最初解决的是多实例系统中的资源利用率、吞吐量、延迟和容错问题。NGINX 官方文档对 HTTP 负载均衡的概括非常典型:在多个应用实例之间做负载均衡,是优化资源利用、最大化吞吐、降低延迟、实现容错配置的常用技术。(NGINX 文档)

但在微服务架构中,负载均衡的复杂度被显著放大,主要原因有四个。

第一,服务实例是动态的。在 Kubernetes 中,Deployment 可以动态创建和销毁 Pod,某一时刻可用的后端集合会不断变化;Kubernetes Service 通过 selector 和 EndpointSlice 来维护服务端点集合。(Kubernetes)

第二,调用不再是少量入口流量,而是大量内部服务间调用。一个用户请求进入系统后,可能触发几十个内部 RPC。如果每一次内部调用都绕到集中式网关,系统会人为增加网络跳数和排队点。

第三,协议发生了变化。HTTP/2、gRPC、长连接、连接池、双向流式调用使“按连接负载均衡”经常失效。gRPC 官方设计文档明确强调,gRPC 内部负载均衡发生在 per-call 维度,而不是 per-connection 维度;即使所有请求来自同一个客户端,也希望这些调用能被分散到所有服务端。(GitHub)

第四,故障治理已经成为负载均衡的一部分。一个好的负载均衡器不只是选择实例,还要配合超时、重试、熔断、连接池、异常实例摘除、预热、灰度、就近路由和指标反馈。Envoy 官方文档将负载均衡策略、异常检测、熔断等都作为 upstream 管理的一部分;Envoy 的 outlier detection 会把异常主机标记为不健康并从负载均衡选择中摘除一段时间。(Envoy Proxy)


2. 网关负载均衡和客户端负载均衡的好坏

本文中的“网关负载均衡”指集中式代理或入口式负载均衡,例如 NGINX、HAProxy、AWS ALB/NLB、Kubernetes Ingress、API Gateway、Service LoadBalancer 等;“客户端负载均衡”指调用方 SDK、gRPC channel、Spring Cloud LoadBalancer,或者与服务进程同节点/同 Pod 运行的 sidecar 代理,例如 Envoy/Istio sidecar,根据服务发现结果直接选择目标实例。

2.1 网关负载均衡的优点

网关负载均衡的最大优点是治理集中。TLS 终止、认证鉴权、WAF、限流、访问日志、黑白名单、路径路由、域名路由等能力天然适合放在入口网关。AWS Application Load Balancer 官方文档说明,ALB 工作在 OSI 第七层,接收请求后按 listener rules 判断规则,并可基于应用流量内容把请求路由到不同 target group。(AWS Documentation)

第二个优点是客户端简单。调用方只需要访问一个稳定域名或虚拟 IP,不需要感知后端实例列表。Kubernetes Service 也体现了这种抽象:Service 把一组 Pod 暴露为一个稳定网络服务,使客户端不必自己追踪后端 Pod 集合变化。(Kubernetes)

第三个优点是入口安全边界清晰。互联网流量、跨网络流量、第三方调用、BFF/API 聚合层都适合先进网关,再进入内部服务网格或服务集群。

2.2 网关负载均衡的缺点

网关负载均衡用于入口是正确的,但用于所有内部服务间调用就明显不合格。

第一,网关容易变成中心瓶颈和故障放大器。所有内部 RPC 都绕网关,会让网关承担本不该承担的东西向流量,任何网关抖动都会影响大量服务之间的调用。

第二,多一次网络跳数,多一次排队和故障点。内部调用本来可以 client → backend,集中式网关会变成 client → gateway → backend。这个额外跳数对高 QPS、低延迟系统很不划算。

第三,网关难以掌握每个调用方的细粒度上下文。不同调用方的超时预算、重试预算、调用优先级、流量权重、灰度规则可能不同。集中网关可以做一部分治理,但会把规则压成复杂的大型配置,长期看会变成运维和治理债务。

第四,长连接和 HTTP/2/gRPC 场景下,传统网关负载均衡可能不均匀。gRPC 官方说明,默认 pick_first 策略实际上不做负载均衡,而是连接 name resolver 返回的第一个可连地址;切换到 round_robin 后,客户端会连接所有地址并在后端之间轮转 RPC。(gRPC) 这说明对 gRPC 这类协议,真正有效的均衡应发生在客户端 channel 或代理的请求选择层,而不是只在入口连接层做一次选择。

2.3 客户端负载均衡的优点

客户端负载均衡的最大优点是决策靠近调用方。gRPC 负载均衡策略接收 resolver 更新的服务器地址列表,为服务器地址创建 subchannel,并在每个 RPC 发送时决定使用哪个 subchannel。(GitHub)

第二个优点是天然水平扩展。负载均衡决策分散在每个客户端或每个 sidecar 上,不再由一个中心网关承载所有内部流量。

第三个优点是适合服务发现和动态端点。Spring Cloud 官方文档明确称 Spring Cloud LoadBalancer 提供自己的 client-side load-balancer 抽象和实现,并通过 ServiceInstanceListSupplier 从 Service Discovery 获取可用实例。(Home)

第四个优点是可以承载更细的流量治理。Istio 官方文档说明,Istio 的流量管理模型依赖随服务部署的 Envoy 代理,mesh 服务发送和接收的流量都会经过 Envoy,从而可以在不修改服务代码的情况下控制流量。(Istio)

2.4 客户端负载均衡的缺点

客户端负载均衡也不是没有代价。

第一,客户端复杂度上升。如果把逻辑写进业务 SDK,不同语言、不同版本、不同服务可能出现策略不一致。

第二,需要可靠的服务发现和配置分发。客户端必须拿到及时、正确、可用的端点列表,否则会发生路由到已下线实例、端点列表陈旧、流量打偏等问题。

第三,可观测性和治理必须标准化。如果每个业务团队自己实现客户端负载均衡,指标、日志、重试、熔断都会碎片化。

因此,最佳实践不是“每个业务自己手写客户端负载均衡”,而是:优先使用成熟框架或 sidecar 数据面统一实现,例如 gRPC 内建策略、Spring Cloud LoadBalancer、Envoy/Istio;不要在业务代码里重复造轮子。


3. 为什么要选择客户端负载均衡而不是网关负载均衡

我的判断很明确:在内部服务间调用场景,应优先选择客户端负载均衡,而不是网关负载均衡。 原因如下。

3.1 客户端负载均衡更适合东西向流量

内部服务调用的特点是高频、低延迟、链路长、依赖多。把这些调用全部集中到网关,会让网关承担不必要的流量中转职责。客户端负载均衡让调用方直接选择后端实例,减少中间跳数,也减少中心节点的排队风险。

Istio 官方文档对 sidecar 模式的描述很关键:服务网格内的流量通过与服务一起部署的 Envoy 代理处理,Envoy 代理使用服务注册表将流量引导到相关服务;这本质上就是“客户端侧代理负载均衡”。(Istio)

3.2 客户端负载均衡能做真正的 per-RPC 均衡

对 gRPC、HTTP/2、连接池场景,按连接均衡经常不能代表按请求均衡。gRPC 官方文档明确将负载均衡策略放在 name resolution 和连接 server 之间,并且由策略在每个 RPC 发送时决定 subchannel。(GitHub)

所以,若系统大量使用 gRPC,继续依赖网关的连接级负载均衡是不专业的。正确做法是配置客户端或 sidecar 的请求级负载均衡策略,例如 round_robinleast_request、xDS/Envoy 策略等。

3.3 客户端负载均衡更容易结合实时健康状态

Envoy 支持 outlier detection:当主机被判定为异常时,会被标记为不健康并从负载均衡选择中摘除;之后可在满足条件后自动回到服务池。(Envoy Proxy) Envoy 还支持分布式熔断,官方文档强调,在分布式系统中快速失败并向下游施加背压通常比继续排队更好。(Envoy Proxy)

这类能力在集中式网关也能做,但在东西向流量中放到客户端侧或 sidecar 侧更自然,因为故障影响可以被限制在具体调用方、具体目标服务、具体优先级和具体连接池范围内。

3.4 客户端负载均衡更适合灰度、版本路由和服务级策略

Istio 官方文档说明,DestinationRule 可用于定制目标服务或服务子集的 Envoy traffic policy,包括负载均衡模型、TLS 模式和熔断设置。(Istio) 这意味着内部服务调用可以按服务、版本、端口、subset 维度配置策略,而不必把所有规则堆到一个大网关里。

3.5 网关仍然应该保留,但它不该承担所有内部调用

正确架构不是“客户端负载均衡消灭网关”,而是分层:

text
External Client

Gateway / Ingress / ALB / API Gateway

Internal Service A
    ↓ client-side LB or sidecar LB
Internal Service B / C / D

结论:入口用网关,内部用客户端负载均衡。 这不是折中,这是成熟微服务系统的基本分工。


4. 负载均衡的算法有哪些?

下面列出主流负载均衡算法及适用场景。

算法原理适用场景不适用场景
Round Robin按顺序轮流选择后端实例后端容量相近、请求耗时相近、短连接或普通 HTTP API请求耗时差异大、实例容量差异大、长连接
Weighted Round Robin根据权重轮询,权重高的实例获得更多请求实例规格不同、灰度放量、容量分层权重维护不准确、实例负载实时波动大
Random随机选择健康后端大规模后端池、实现简单、多个 LB 同时存在小规模服务、请求耗时差异大
Least Connections选择当前连接数最少的实例长连接、数据库连接、TCP 长会话HTTP 短请求、HTTP/2 多路复用时连接数不等于请求负载
Least Request / Least Outstanding Requests选择当前 in-flight 请求最少的实例,或从随机候选中选负载更低者微服务 HTTP/RPC、请求耗时差异大、实例性能有差异指标不可用或请求极短且完全均质时收益有限
Power of Two Choices / P2C随机选两个或 N 个候选,再选负载更低者大规模服务池,性能和均衡效果折中很好需要维护实时负载信息
IP Hash / Source Hash根据客户端 IP 或源信息 hash 到固定实例需要会话亲和、TCP 场景客户端 IP 分布不均、NAT 大量用户共用 IP
Consistent Hash / Ring Hash将请求 key 和后端映射到 hash ring,后端变化时减少重映射缓存、会话亲和、状态相关服务无状态服务默认不该用,会牺牲均衡性
Maglev Hash一种一致性哈希,目标是减少后端变化时的扰动大规模一致性路由、服务网格、边缘代理普通无状态 API 默认没必要
Locality / Zone-aware LB优先同机房、同可用区、同地域实例多 AZ、多地域、跨云部署容量不足时若没有 failover 会打满局部资源
Adaptive / Client-side Weighted RR根据后端上报的负载、错误率、利用率动态调整权重服务能力差异大、负载波动明显没有可靠指标或反馈延迟过大

NGINX Open Source 官方支持 Round Robin、Least Connections、IP Hash、Generic Hash,NGINX Plus 还支持 Least Time 和 Random;其文档明确指出 Round Robin 是默认方法。(NGINX 文档)

Envoy 官方支持 Weighted Round Robin、Client-side Weighted Round Robin、Weighted Least Request、Ring Hash、Maglev 和 Random。Envoy 的 Weighted Least Request 在等权重情况下使用 P2C 思路,即随机选择若干可用主机,再挑 active requests 更少的主机。(Envoy Proxy) Envoy 对 Ring Hash 和 Maglev 的说明也表明,一致性哈希主要适合需要稳定 hash key 的场景。(Envoy Proxy)

AWS ALB 官方支持 Round Robin、Least Outstanding Requests、Weighted Random,并说明 Round Robin 是 target group 级别的默认路由算法;Least Outstanding Requests 会把请求路由给 in-progress 请求数最低的目标。(AWS Documentation)

HAProxy 官方配置手册中也列出 roundrobin、leastconn、source、uri、url_param、hdr、random 等算法,并称 roundrobin 会按权重轮流使用服务器,在服务器处理时间分布相近时是平滑且公平的算法。(HAProxy Technologies)


5. 业界使用最标准、最广泛的负载均衡算法是哪个?

最标准、最广泛的基线算法是 Round Robin,生产增强版通常是 Weighted Round Robin。

这个结论不是因为 Round Robin 最聪明,而是因为它最简单、最可解释、最容易实现、跨产品支持最广。NGINX 默认使用 Round Robin;AWS ALB 的 target group 默认算法也是 Round Robin;Envoy 支持 Weighted Round Robin;HAProxy 也把 roundrobin 作为核心调度算法之一。(NGINX 文档)

但必须强调:“最广泛”不等于“生产最优”。 对现代微服务 HTTP/RPC 来说,我更推荐把 Least Request / P2C 作为默认生产策略,尤其是请求耗时不均、实例负载不均、存在扩缩容和冷启动的场景。Istio 官方文档已经非常直接地说,ROUND_ROBIN 在许多场景下通常不安全,可能使端点过载;一般应优先使用 LEAST_REQUEST 作为 ROUND_ROBIN 的替代,并称 LEAST_REQUEST 通常更安全且几乎总是优于 ROUND_ROBIN。(Istio)

因此本文给出明确判断:

text
行业最标准、最广泛:Round Robin / Weighted Round Robin
现代微服务更推荐:Least Request / P2C
需要会话亲和或缓存命中:Consistent Hash / Ring Hash / Maglev
异构容量或灰度放量:Weighted Round Robin / Adaptive Weighted

6. 当前客户端负载均衡最佳实践

6.1 架构原则:入口集中,内部下沉

最合理的职责划分如下:

text
North-South Traffic:
User / Partner / Mobile / Web
    → Gateway / Ingress / ALB / API Gateway
    → Internal Service

East-West Traffic:
Service A
    → Client-side LB / Sidecar LB
    → Service B instances

网关负责入口治理,客户端负载均衡负责内部调用。把网关当成所有内部服务调用的中转中心,是不应该采用的设计。

6.2 优先使用 sidecar 或成熟客户端框架

如果系统是多语言、多团队、多服务,最佳选择是 Envoy/Istio 这类 sidecar 数据面,因为它能避免各语言 SDK 分裂。Envoy 官方介绍中说明,Envoy 可作为 edge proxy 和 service proxy,运行在每个应用旁边,并以平台无关方式提供通用网络能力。(Envoy Proxy)

如果系统主要是 Java/Spring,可以使用 Spring Cloud LoadBalancer。Spring 官方文档说明它提供 client-side load-balancer 抽象,默认实现是 RoundRobinLoadBalancer,也可切换到 RandomLoadBalancer。(Home)

如果系统大量使用 gRPC,应显式配置 gRPC 客户端负载均衡策略。不要默认依赖 pick_first,因为 gRPC 官方文档已经说明 pick_first 实际上不做负载均衡。(gRPC)

6.3 算法选择建议

默认建议:

text
普通微服务 HTTP/RPC:Least Request / P2C
完全均质短请求:Round Robin / Weighted Round Robin
gRPC 多实例调用:round_robin 或 xDS/Envoy 策略
缓存或会话亲和:Consistent Hash / Ring Hash / Maglev
异构实例规格:Weighted Round Robin 或 Adaptive Weighted
多地域/多 AZ:Locality-aware + failover

对于 Istio,建议优先配置:

yaml
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
  name: order-service-lb
spec:
  host: order-service.default.svc.cluster.local
  trafficPolicy:
    loadBalancer:
      simple: LEAST_REQUEST

Istio 官方文档明确说明 DestinationRule 可配置目标服务的负载均衡策略,并列出 Random、Weighted、Round robin、Consistent hash、Ring hash、Maglev 等选项。(Istio)

6.4 健康检查必须分主动和被动

负载均衡不能只看“实例是否存在”,必须看“实例是否还能正确服务”。

主动健康检查适合周期性确认实例是否存活;被动异常检测适合根据真实流量中的 5xx、超时、reset 等错误摘除异常实例。Envoy 官方文档说明,outlier detection 可以基于连续 5xx、网关错误、本地连接失败、成功率、失败百分比等检测异常主机。(Envoy Proxy)

最佳实践:

text
必须做:
1. Readiness / active health check
2. Passive outlier detection
3. Connection draining
4. Slow start / warmup
5. Endpoint ejection metrics

6.5 熔断、超时、重试必须和负载均衡一起设计

只做负载均衡,不做熔断和重试预算,是半成品。Envoy 官方文档明确说,熔断是分布式系统关键组件,快速失败并向下游施加背压通常更好;Envoy 支持 cluster 最大连接数、最大 pending requests、最大 active requests、最大 retries 等限制。(Envoy Proxy)

推荐规则:

text
1. 每个服务必须有默认超时。
2. 重试只允许幂等接口或明确可重试错误。
3. 重试必须设置 retry budget,不能无限重试。
4. 熔断按目标服务、优先级、调用方分层配置。
5. 过载时宁可快速失败,也不要无限排队。

6.6 gRPC 场景不要使用默认 pick_first

gRPC 的默认 pick_first 不是负载均衡。一个最小的 gRPC service config 示例:

json
{
  "loadBalancingConfig": [
    {
      "round_robin": {}
    }
  ],
  "methodConfig": [
    {
      "name": [
        {
          "service": "com.example.OrderService"
        }
      ],
      "timeout": "2s"
    }
  ]
}

gRPC 官方文档说明,round_robin 会连接它拿到的每个地址,并在已连接的后端之间为每个 RPC 轮转。(gRPC)

6.7 Spring Cloud 场景要避免隐式懒加载抖动

Spring Cloud LoadBalancer 默认会为每个 service id 创建子上下文,并在第一次请求时懒加载。生产系统中,对核心依赖服务建议 eager load,避免首次调用抖动。Spring 官方文档说明可以通过 spring.cloud.loadbalancer.eager-load.clients 配置需要提前加载的 service id。(Home)

yaml
spring:
  cloud:
    loadbalancer:
      eager-load:
        clients:
          - order-service
          - payment-service

6.8 可观测性必须覆盖“选择过程”

客户端负载均衡上线后,不能只看服务总体 QPS。至少要看:

text
1. 每个 endpoint 的 request count
2. 每个 endpoint 的 active requests
3. 每个 endpoint 的 p95 / p99 latency
4. 每个 endpoint 的 error rate
5. endpoint ejection count
6. retry count and retry success rate
7. circuit breaker overflow count
8. load balancing policy distribution
9. locality / zone hit ratio
10. endpoint list freshness

没有这些指标,客户端负载均衡一旦打偏,很难排查。


7. 推荐落地步骤

第一步:区分流量类型

text
外部用户 → 系统:网关负载均衡
服务 A → 服务 B:客户端负载均衡或 sidecar 负载均衡
跨集群 / 跨地域:全局流量调度 + 本地客户端负载均衡

第二步:选择实现方式

text
多语言微服务:Istio / Envoy sidecar
gRPC 为主:gRPC round_robin / xDS / Envoy
Java Spring 为主:Spring Cloud LoadBalancer
Kubernetes 原生简单系统:Service + EndpointSlice + 客户端服务发现

第三步:选择默认算法

text
生产默认:LEAST_REQUEST / P2C
保守基线:Weighted Round Robin
会话亲和:Consistent Hash
缓存服务:Ring Hash / Maglev
异构容量:Weighted / Adaptive Weighted

第四步:配置故障治理

text
1. timeout
2. retry budget
3. circuit breaker
4. passive outlier detection
5. active health check
6. slow start / warmup
7. connection draining

第五步:灰度迁移

text
1. 先对低风险服务启用客户端负载均衡。
2. 对比网关路径和客户端直连路径的延迟、错误率、重试率。
3. 小比例切流。
4. 观察 endpoint 分布是否均匀。
5. 逐步替换内部网关中转调用。
6. 保留入口网关,不要移除南北向治理。

8. 结论

当前客户端负载均衡的最佳实践可以概括为一句话:

入口流量用网关,内部调用用客户端负载均衡;算法上以 Round Robin / Weighted Round Robin 作为行业基线,以 Least Request / P2C 作为现代微服务默认优选,以 Consistent Hash 服务状态亲和场景。

对现代微服务系统来说,继续把所有内部调用都压到网关做集中负载均衡,是一种架构退化。正确的做法是将负载均衡决策下沉到客户端或 sidecar,让每个调用方基于服务发现、健康状态、连接池、熔断、重试和调用上下文做更近、更快、更细粒度的决策。网关仍然重要,但它应该守住入口,而不是统治所有内部流量。

GitHub Discussions

参与讨论

评论会同步到 stellhub/stell-web 仓库的 GitHub Discussions。

Powered by VitePress and GitHub Discussions.