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 进程。正确的步骤是:
- 从注册中心摘除:告诉负载均衡器“别给我发新请求了”。
- 等待排空:让正在处理的请求完成,通常等待一个超时时间(比如 30 秒)。
- 关闭监听:停止接受新连接。
- 清理资源:关闭数据库连接、释放内存等。
- 进程退出。
我曾经踩过一个坑:摘除注册中心后,以为流量马上会停。结果负载均衡器有缓存,还在往这个节点发请求。所以等待排空的时间一定要大于负载均衡器的缓存刷新时间。
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% 的流量,然后逐步增加。
健康检查与自动摘除,是整个高可用体系的“免疫系统”。它不直接产生价值,但没有它,其他高可用手段都是空中楼阁。把这一块做扎实了,你的系统才算真正有了自愈能力。