暗无天日

=============>DarkSun的个人博客

TIL:服务端 vs 客户端负载均衡的健康检查机制

服务端负载均衡(中心代理)和客户端负载均衡(注册表 + 自路由)的健康检查机制完全不是一回事。选哪种方式,决定了系统多快发现故障、多准响应异常,以及多少复杂性会漏到应用代码里。

原文 The Instance Is Up. Or Is It? — Health Checking in Client-Side vs Server-Side Load Balancing

两种 LB 模型

服务端 LB

HAProxy、NGINX、AWS ALB 这类方案。专用代理挡在客户端和后端之间,客户端只知道一个地址就够了。LB 持有后端池的完整视图,探测健康状态、维护连接池、分配流量,后端的事全归它管。客户端对后端拓扑完全无感。

客户端 LB

gRPC 内置 LB、Netflix Ribbon、LinkedIn D2 这类方案。路由决策搬到客户端,每个客户端从注册表拉取后端实例列表,每次请求自己做路由。请求路径上没有代理。注册表维护权威实例列表,客户端订阅更新后在本地维护路由表。

三种健康检查方式

两种模型下的健康检查机制可以归成三类,各有各的取舍。

服务端主动探测

LB 按固定周期去探测后端,常见的有 TCP 连接检查、HTTP /health 端点、自定义命令这些。典型配置大概是,探测间隔 5 秒,超时 2 秒,rise 阈值连续 2 次成功记为健康,fall 阈值连续 3 次失败记为不健康。

这些阈值是为了防止 flapping(单次临时失败就把实例踢出轮换)。代价是延迟。5 秒间隔乘以 3 次 fall 阈值,一个硬故障最长 15 秒才能被检出。这 15 秒里流量还在往坏掉的实例上打。

LB 一旦标记不健康,会立即把它从轮换中摘掉。不需要通知客户端,所有调用方同时生效。

客户端主动探测

轮到客户端了。没有中心代理,每个客户端(或它的 sidecar)独立探测它知道的所有实例。跟服务端 LB 一样跑探测循环,只是每个客户端都跑。500 个客户端乘以 20 个实例再乘以每 5 秒一次,光健康信号就每秒产生 2000 次探测请求。

每个客户端独立探测,判断结果自然也不一样。同一个实例,客户端 A 刚测完还能响应,客户端 B 过一秒再测已经超时了。尤其在实例开始出问题但还没彻底挂掉的阶段,不同客户端拿到不同结果很正常。等实例彻底死了以后,大家最终都会把它标记为不健康,但在那之前,各个客户端心里装的健康图景可能不一样。

客户端被动检测(也叫离群检测、失败追踪)

客户端不做主动探测,直接观察真实请求的结果。连接被拒、超时、连续 500 错误,这些都说明服务实例出问题了。客户端把它标记为不健康,停止向其发送流量一段时间(backoff)。

被动检测最大的好处是没有轮询延迟。第一个失败请求就能立即触发响应。坏处是至少有一个真实请求必须失败才能触发反应。高吞吐系统里这通常可以接受,但低流量或突发场景中,用户就会看到更多报错。

怎么选

没有通用的答案。取决于集群规模、调用频率、运维能力,还有你在客户端库里能维护多复杂的逻辑。

服务端 LB 更简单,适合大多数团队做默认选择。客户端 LB 的收益体现在规模大到让中心代理成为瓶颈的时候。可能是代理本身扛不住了,也可能是需要亚毫秒级故障检测,又或者多一跳的延迟已经变得不可忽视了。

至于客户端 LB 内部选主动还是被动检测,看你对探测开销和故障容忍度怎么权衡。主动检测会在每个客户端上产生额外的探测流量(N×M 请求放大),但不影响真实请求,适合低流量系统。被动检测没有额外开销,但必须等到真实请求失败才能发现故障,适合高吞吐系统,偶尔失败几个请求换来更低的开销。两者不互斥,很多系统同时开,主动做保底,被动做快速反应。

实践中大型系统往往两头都用,入口层用服务端 LB(客户端是外部不可控的),服务间调用用客户端 LB(客户端库统一可控)。

编程之旅 : 负载均衡 : 健康检查 : 分布式系统