在分布式系统运维中,一个反直觉的现象经常让人困惑:后端服务已经无法响应请求甚至进程已经退出,负载均衡器的健康检查却仍然显示「正常」,流量依然被路由过去。这种「死亡」后端持续接收请求的场景,往往会导致 502/504 错误,进而引发业务中断。本文将从 TCP 探测超时、优雅关闭缺失、连接耗尽三个核心维度,解析健康检查失效的底层机制,并给出可操作的配置参数与监控策略。
健康检查与实际流量的语义鸿沟
理解健康检查失效的第一步,是认识到健康检查探测的端口状态与后端实际处理请求的能力之间存在显著差异。大多数负载均衡器默认使用 TCP 端口检测或轻量级 HTTP GET 请求(如 GET /healthz)来判断后端是否存活。这类探测只验证 TCP 三次握手能否成功建立,或目标路径是否返回 200 状态码,并不检验后端的业务处理能力。
一个典型的失效场景是:后端进程虽然仍然持有监听端口(能够完成 TCP 握手),但已陷入死循环、数据库连接池耗尽或资源耗尽的状态。此时 TCP 健康检查会通过,因为端口确实在监听;然而实际业务请求会因为超时或连接拒绝而失败。另一种常见情况是健康检查路径配置错误 —— 如果检查路径是 /health 而应用实际暴露的是 /healthz,或者期望状态码是 200 但实际返回 302 重定向,健康检查会持续失败,导致后端被错误地从池中移除。
这种语义鸿沟的根源在于健康检查本质上是一种「近似探测」,它必须在检测精度与检测成本之间取得平衡。过于复杂的健康检查会增加负载均衡器的计算开销,而过于简单的检查则可能遗漏实际故障。正确的做法是将健康检查视为「最低可用性」的门槛,业务逻辑的完整性应由更上层的机制(如超时重试、熔断器)来保障。
优雅关闭缺失导致的流量劫持
当后端需要重启、部署新版本或主动下线时,如果没有配置连接 draining(也称为连接耗尽、优雅退出),负载均衡器会面临两难选择:立即停止向该后端发送新请求会中断正在处理中的请求(导致用户看到错误),而继续发送请求则可能发送到已经无法处理的实例。
优雅关闭的核心机制分为两个阶段。首先,负载均衡器在检测到后端即将下线或健康检查首次失败后,应该停止向其发送新请求,但保留现有连接直到请求完成或超时。这段缓冲时间即为 draining timeout。其次,后端进程在收到 SIGTERM 信号后,应立即停止接受新连接,将状态标记为 draining,并等待正在处理中的请求完成后再退出。如果应用进程在 SIGTERM 后立即退出而不是等待请求完成,即使 draining timeout 配置再长也无法生效。
实际工程中,常见的错误配置包括:draining timeout 设置过短(如 5 秒),而 95 百分位请求处理时间超过 30 秒;应用没有正确处理 SIGTERM 信号,接收到终止信号后立即关闭而非等待;或者负载均衡器与后端之间缺乏关于连接状态的同步机制,导致后端已关闭但负载均衡器仍然认为连接可用。AWS ALB 默认的 deregistration delay 为 300 秒,Nginx upstream 模块的 slow_start 参数可以逐步增加新恢复后端的权重,这些都是针对优雅关闭的配置选项。
连接耗尽与 TCP 连接状态错配
第三个导致健康检查失效的根因是连接耗尽(connection exhaustion)。当后端服务器的可用连接数达到上限时,新的请求会被直接拒绝或排队等待。然而,负载均衡器的 TCP 健康检查通常只发送单个探测包并等待响应,由于探测请求数量有限,往往不会触发连接数上限,因此健康检查可以通过,但实际业务请求却无法建立连接。
这种状态错配的另一个表现形式是空闲超时(idle timeout)与 keep-alive 的冲突。如果后端的 HTTP keep-alive 超时时间短于负载均衡器的空闲超时,後端会先于负载均衡器关闭连接(发送 FIN 包),此时负载均衡器仍然认为该连接可用,当尝试复用该连接发送下一个请求时会遭遇连接已关闭的错误。反之,如果负载均衡器的空闲超时设置过短,而应用的处理时间较长,负载均衡器会在业务处理完成前主动关闭连接,导致客户端收到意外的中断响应。
连接耗尽的监控需要关注几个关键指标:后端的 ESTABLISHED 连接数、TIME_WAIT 状态的连接堆积量、SYN_RECV 状态的半开连接队列长度,以及文件描述符的使用率。当这些指标接近系统限制时,即使健康检查通过,后端也已经无法处理新的请求。建议在监控系统中设置告警阈值,例如当 ESTABLISHED 连接数达到最大连接数的 80% 时触发预警。
可落地的参数配置清单
基于上述三个根因,以下是生产环境中推荐的关键参数配置,这些数值适用于大多数 Web 应用场景,实际部署时可根据业务请求的典型处理时间进行调整。
健康检查配置方面,HTTP 健康检查路径应选择无状态依赖的端点(如 /healthz 或 /readyz),只检查进程存活和基本资源状态,不应包含数据库调用或外部服务依赖。健康检查超时建议设置为 2 至 5 秒,间隔 10 至 30 秒,不健康阈值 3 至 5 次,连续失败后从池中移除。对于 TCP 健康检查,建议使用 tcp_check 配合 connect_timeout 参数,同样避免复杂的状态验证。
优雅关闭配置方面,draining timeout(也称为 deregistration delay 或 connection draining timeout)应设置为高于 95 百分位请求处理时间的 1.5 至 2 倍,如果 P95 请求时间为 20 秒,则 draining timeout 建议设为 30 至 40 秒。后端应用必须正确处理 SIGTERM 信号,在收到终止信号后立即停止接受新连接,等待现有请求完成后退出,最大退出等待时间应小于 draining timeout。
超时与连接配置方面,负载均衡器的空闲超时应设置为后端 keep-alive 超时时间的 1.2 至 1.5 倍,例如后端 keep-alive 为 60 秒则空闲超时设为 75 至 90 秒。连接超时(connection timeout)应小于后端处理超时,推荐前者为后者的 70% 左右。最大连接数应根据后端实例规格和单请求内存占用计算,确保在峰值流量下仍有 20% 的余量。
监控与告警配置方面,建议在负载均衡器侧监控后端健康检查失败率、后端响应时间分布、502/504 错误占比;在后端侧监控连接数状态分布、CPU / 内存利用率、文件描述符使用率、进程健康状态。当健康检查通过率低于 99.5% 或 502 错误率超过 1% 时应触发告警。
小结
负载均衡器向「死亡」后端发送流量的根本原因,可以归结为健康检查的语义局限、优雅关闭机制的缺失、连接状态与资源状态的错配这三个维度。在工程实践中,不能简单依赖健康检查作为后端可用性的唯一判据,而需要结合优雅关闭配置、超时参数对齐、连接数监控等多层防护。当健康检查通过但业务请求失败时,应优先检查 draining 机制是否生效、连接是否接近耗尽、以及空闲超时与 keep-alive 的配置是否匹配。通过本文给出的参数清单,运维团队可以快速诊断并修复这类隐蔽但危害严重的路由异常。
参考资料
- AWS 官方文档:Application Load Balancer 故障排查(AWS Documentation)
- DigitalOcean:负载均衡器健康检查问题故障排查(DigitalOcean Support)