4. 健康检查与自动摘除:主动健康检查、被动健康检查、优雅上下线、自动摘除与恢复

健康检查这东西,听起来简单,做起来全是坑。

我见过太多团队,上线时把健康检查配上了,然后就再也不管了。结果呢?某天流量高峰,一台机器内存快爆了,健康检查还在报“OK”,因为那个检查只看了端口是否存活。流量继续往里打,最终整个集群雪崩。

说白了,健康检查不是“活着就行”,而是“能不能好好干活”。

4.1 主动健康检查:你主动问,它被动答

主动健康检查,就是你的负载均衡器或者注册中心,定期去“敲门”问服务:“嘿,你还活着吗?能处理请求吗?”

我个人习惯把主动检查分成三个层级:

层级 检查内容 典型实现
L4 检查 TCP 端口是否监听 telnet、TCP SYN
L7 检查 HTTP 返回码、响应时间 /healthz 端点
业务检查 核心依赖是否正常、数据是否可读 /ready 端点

很多团队只做到 L4 检查,这远远不够。我在项目中遇到过,数据库连接池满了,端口还活着,但新请求进来全超时。L4 检查完全看不出来。

我的建议:至少做到 L7 检查。在 /healthz 接口里,除了返回 200,还要检查关键依赖的健康状态。比如 Redis 能不能连、本地缓存有没有过期、模型文件是否加载完成。

主动检查的另一个关键点是频率和超时。频率太高,服务本身会被打垮;频率太低,故障发现太慢。我一般用 5 秒一次,超时 2 秒,连续失败 3 次判定为不健康。

4.2 被动健康检查:让流量自己说话

主动检查是“我猜你不行”,被动检查是“你证明你不行”。

被动健康检查不主动发请求,而是观察实际流量的表现。比如:

  • 连续返回 5xx 错误
  • 请求超时比例超过阈值
  • 连接被拒绝

一旦触发这些条件,就把节点从负载均衡池里摘掉。

你想想看,主动检查可能漏掉一些场景。比如服务本身健康,但网络链路有问题,主动检查走的是内网,用户请求走的是公网,结果完全不同。被动检查能捕捉到这种“真实流量视角”的故障。

经验之谈:被动检查的熔断器(Circuit Breaker)实现,我推荐用滑动窗口算法。统计过去 10 秒内的错误率,而不是简单的计数器。这样可以避免瞬间抖动导致误判。

被动检查有个缺点——它需要“先受伤再反应”。所以不能单独用,必须和主动检查配合。

4.3 优雅上下线:别让用户感受到你的变化

优雅上下线,说白了就是“走的时候别摔门,来的时候别撞人”。

4.3.1 优雅下线

服务要下线时,不能直接 kill 进程。正确的步骤是:

  1. 从注册中心摘除:告诉负载均衡器“别给我发新请求了”。
  2. 等待排空:让正在处理的请求完成,通常等待一个超时时间(比如 30 秒)。
  3. 关闭监听:停止接受新连接。
  4. 清理资源:关闭数据库连接、释放内存等。
  5. 进程退出。

我曾经踩过一个坑:摘除注册中心后,以为流量马上会停。结果负载均衡器有缓存,还在往这个节点发请求。所以等待排空的时间一定要大于负载均衡器的缓存刷新时间。

4.3.2 优雅上线

新节点上线时,不能一上来就接满流量。我习惯用“预热”策略:

  • 先注册到注册中心,但标记为“预热中”。
  • 负载均衡器只分配 10% 的流量给它。
  • 观察 1-2 分钟,如果没有异常,逐步提升到 100%。

注意:Java 应用有 JIT 预热问题,刚启动时性能很差。如果直接接满流量,很可能触发超时雪崩。预热不是可选项,是必选项。

4.4 自动摘除与恢复:让系统自愈

自动摘除是健康检查的最终目的——把不健康的节点踢出去。但摘除之后呢?不能不管了。

4.4.1 摘除策略

策略 触发条件 动作
临时摘除 连续 3 次健康检查失败 从负载均衡池移除,保留在注册中心
永久摘除 连续 10 次失败或 5 分钟内无法恢复 从注册中心完全移除,触发告警
手动摘除 运维人员主动操作 立即摘除,不等待

4.4.2 自动恢复

节点被摘除后,不能永远不回来。自动恢复的逻辑是:

  • 被摘除的节点进入“冷却期”(比如 30 秒)。
  • 冷却期结束后,重新开始健康检查。
  • 如果连续 3 次检查通过,重新加入负载均衡池。
  • 如果再次失败,冷却期加倍(指数退避)。

嗯,这里要注意:自动恢复不能太激进。我见过一个案例,节点因为磁盘 IO 高被摘除,恢复后马上又被摘除,反复震荡。后来加了“最小稳定运行时间”条件——恢复后至少稳定运行 5 分钟,才允许再次自动恢复。

4.5 整体流程:一张图说清楚

下面这张图,把主动检查、被动检查、优雅上下线、摘除恢复串在一起。你可以看到整个生命周期是怎么流转的。

健康检查与自动摘除整体流程 服务实例 优雅上线(预热) 正常运行 主动健康检查 被动健康检查 健康? 继续运行 自动摘除 冷却期(指数退避) 恢复检查(回到健康判断) 说明 虚线表示自动恢复路径

4.6 避坑指南

最后,分享几个我踩过的坑,希望能帮你少走弯路。

坑一:健康检查接口和业务接口共用线程池。

我曾经遇到过,业务线程池满了,健康检查请求也进不去,导致健康检查失败,节点被摘除。但实际上业务只是慢,不是死。解决方案:健康检查用独立的线程池,或者用单独的端口。

坑二:摘除后没有通知周边系统。

节点被摘除了,但监控系统还在告警,日志采集还在发数据。摘除后要同步通知:停止告警、停止日志采集、释放资源。

坑三:恢复时没有做流量控制。

节点恢复后,如果瞬间接满流量,可能再次被打垮。我习惯用“慢启动”策略——恢复后的前 30 秒,只分配 20% 的流量,然后逐步增加。

健康检查与自动摘除,是整个高可用体系的“免疫系统”。它不直接产生价值,但没有它,其他高可用手段都是空中楼阁。把这一块做扎实了,你的系统才算真正有了自愈能力。