Skip to main content

参考:负载均衡与服务发现

一份关于负载均衡与服务发现的参考——L4 与 L7、均衡算法、健康检查与连接排空、N+1 余量,以及长连接 gRPC 在 L4 均衡器后面的陷阱。

它是什么

负载均衡器把请求分散到一池服务器上,让没有哪台过载,并且跳过已经挂掉的。服务发现回答的是先于它的那个问题:*此刻有哪些服务器存在、它们在哪里?*在一个实例每隔几分钟就来去一次的系统里,二者合起来,才让客户端不必知道任何地址,也能找到”订单服务”。

Important

核心要点: 选择层(L4 追求原始吞吐和非 HTTP;L7 按内容做路由),按请求的不均匀程度选择算法(请求差异大时,least-loaded 或 power-of-two-choices 优于 round robin),并且一定要搭配健康检查和连接排空。保留余量,让丢失一个实例不会压垮其余的。

何时需要它

每个设计里都有一个负载均衡器的方框,追问是”服务器挂了会怎样”、“怎么让用户停留在同一台服务器上”、“服务之间怎么互相找到”。经典的陷阱是在 L4 均衡器后面跑长连接的 HTTP/2(gRPC)。

Note

面试信号: 说出 gRPC 的陷阱(L4 均衡器按连接路由,但 gRPC 在一个长连接上复用大量请求,所以所有流量都落到同一个后端),说明你有实战经验。解决办法是 L7 代理或客户端侧的负载均衡。

Layer 4 与 Layer 7

Layer 4(传输层)Layer 7(应用层)
依据IP 地址和端口请求本身:host、path、header、cookie
速度非常快;每个请求几乎没有额外工作较慢:要解析请求
TLS可以透传通常终止 TLS
路由按连接按请求:按路径、按 header、金丝雀分流
适合吞吐、非 HTTP 协议、固定地址HTTP 和 gRPC API、微服务路由、WebSocket
例子网络负载均衡器、TCP 模式的 HAProxy应用负载均衡器、Envoy、NGINX

gRPC 陷阱。 gRPC 运行在 HTTP/2 上,它在一个长连接 TCP 连接上复用许多调用。L4 均衡器在连接建立时选择一个后端,所以那个客户端的全部流量都去了同一台服务器,增加服务器也没有用。要使用第 7 层代理对每个请求做均衡,或者用客户端侧的负载均衡,让客户端持有到所有后端的连接。

均衡算法

算法做法适用弱点
Round robin依次轮流每个后端请求均匀且很短忽略负载和请求成本
Weighted round robin按权重成比例分配后端规格不同权重是静态的
Least connections打开请求数最少的后端请求时长差异大需要共享状态;多个均衡器时数据过期
Least response time(延迟感知,EWMA)偏向最近最快的后端后端异构或有噪声可能涌向一个快但正在出错的节点
Power of two choices随机挑两个,发给负载更轻的那个有很多均衡器、没有共享状态比全局视角稍欠最优
一致性哈希把一个 key(用户、session、缓存 key)哈希到某个后端缓存局部性或粘性热点 key 会导致负载不均

算例:为什么两次选择就够了。 把 n 个请求随机分配给 n 台服务器时,最忙的服务器大约会拿到 log n ÷ log log n 个请求(在 n 很大时是平均值的好几倍)。随机挑两台服务器,发给负载更轻的那台,会把最大值降到大约 log log n,一次额外的探测换来指数级的改善,而且不需要任何全局共享状态,这就是它适合由独立均衡器组成的集群的原因。

粘性(同一个用户总是到同一个后端)只在状态存放在服务器上时才需要:使用 cookie 或对某个 key 做一致性哈希。更好的是让服务器无状态、状态放在共享存储里;粘性会和均匀负载作对,也会让排空变复杂。

健康检查、排空与余量

  • 主动健康检查按间隔探测每个后端(一个 HTTP 路径、一次 TCP 连接),失败 N 次后把它摘除;成功 M 次后再加回来。被动检查观察真实流量,在出现错误或超时时剔除后端。两者都用。
  • 健康检查端点应检查让实例能够提供服务的东西(它的关键依赖),但要小心:太浅的检查会在应用已经坏掉时仍然通过,太深的检查会在某个共享依赖抖动时让所有实例一起失败(级联故障)。
  • 连接排空: 摘除一个后端时(部署、缩容),停止向它发新请求,但让在途的请求在时限内完成。
  • 慢启动: 逐步加大新后端的流量,这样冷缓存或还没预热的运行时不会被全量负载直接冲击。

算例:余量。 六个实例,利用率 70%。如果有一个挂了,其余五个要承担它的负载:0.70 × 6 ÷ 5 = 84%,而在 84% 时排队延迟已经很陡。如果集群原来就运行在 85% 的利用率,丢失一个会把其余的推到 85% × 6 ÷ 5 = 102%,超出了容量,于是一次故障就变成了一次宕机。要按 N + 1(能容忍丢失一个)或跨可用区的 N + 2 来规划,而不是按平均负载。

全局负载均衡

跨区域时,流量由 DNS(基于地理或延迟的应答,由健康检查把故障区域摘掉)和 anycast(同一个 IP 在许多地方同时宣告,让网络路由到最近的那个)来引导。DNS 的应答会按生存时间被缓存,所以故障切换的速度和 TTL 一样慢;故障切换记录使用短 TTL,是用 DNS 负载换敏捷性。

服务发现

方式做法优点缺点
DNS名称解析到当前的实例(headless service,或每个实例一条记录)通用,不需要客户端库缓存延迟更新;客户端可能钉住一个地址
服务端(前面放一个负载均衡器)客户端调用一个稳定地址;由均衡器掌握实例池客户端简单多一跳;均衡器成为关键基础设施
客户端侧客户端查询一个注册中心(Consul、etcd、Eureka)并自己做均衡没有额外跳数;客户端可以更聪明(延迟感知、可用区感知)每种语言都需要客户端库;依赖注册中心
Service mesh(sidecar 代理)每个服务旁边的代理负责发现、均衡、重试、双向 TLS无需修改应用就有统一的行为运维复杂、增加延迟

实例会注册自己(或由平台代为注册),带上健康状态和心跳;过期的条目会失效。注册中心本身是一个小而关键、由共识支撑的存储。在客户端缓存它的应答,这样注册中心抖动一下不会让流量停下来,并且要绕开在本地失败的实例,而不是只绕开注册中心已经移除的。

Warning

生产实践: 除非你把它们做成冗余的,否则均衡器和注册中心就是单点故障。把均衡器部署在多个可用区,让客户端能容忍过期的注册信息,并且测试注册中心不可用时会发生什么。一种常见的真实事故是:一个健康检查把所有实例同时标记为不健康,在这之后,“fail open”(对所有实例都提供服务)的策略好于对任何一个都不提供服务。

如何选择

情形选择原因
HTTP API、按路径路由、金丝雀发布L7 均衡器按请求做决定
最大吞吐、非 HTTP、固定 IPL4 均衡器快速、简单
gRPC 或其他 HTTP/2 复用L7 或客户端侧均衡避开单连接陷阱
请求成本差异很大Least connections 或 power of two choicesRound robin 忽略成本
缓存层或 session 亲和一致性哈希同一个 key 落在同一节点
服务互相发现注册中心加客户端侧均衡,或 mesh实例不断变化
能承受丢失一个实例按 N + 1 余量规划决定性的是故障后的利用率

Caution

常见错误: 按平均负载规划容量。丢失一个实例(或一个可用区)之后集群还剩多少容量,才决定了一次故障是不是一次事故。另外:依赖某个共享下游的健康检查,可能会把所有实例一起踢掉,把局部故障变成全面故障。

相关