健康检查到底该检查什么
大多数健康端点回答的是没人问过的问题。有用的区分是「这个进程活着吗」和「这个实例现在该不该接流量」—— 把两者混为一谈,会在两个方向上都造成故障。

健康检查到底该检查什么 ❤️
默认的健康端点返回 200 OK 和一个字符串 ok。它证明进程在跑、HTTP 服务器在监听。这是个真实的事实,而且几乎从来不是任何人需要的那个事实。
编排器真正在问的是:我现在该不该把一个用户的请求发给这个实例? 一个进程可以既在运行,又是这个问题的糟糕答案 —— 配置还在加载、握着一个已死的数据库连接、内存在抖动,或者正在关闭的中途。
存活和就绪是两个不同的问题
存活:这个进程是不是坏到无法恢复了? 存活检查失败应当触发重启。因此它必须极度保守 —— 只检查进程本身是否可用,不牵扯任何依赖。一个会查数据库的存活检查,会在数据库抖动时把你所有实例全部重启,把一次降级变成一次全面故障。这是健康检查里代价最高的错误,而它之所以常见,恰恰因为"检查一下数据库"听起来更周全。
就绪:这个实例现在该不该接流量? 就绪检查失败应当把实例移出轮转,但不杀掉它。这一个可以谨慎地考虑依赖,而且它应当反映启动状态:配置已加载、缓存已预热、连接已建立、迁移已应用。
把两者合成一个端点,等于强迫两个正确答案不同的问题共用一个答案。
就绪该检查什么
包含那些特定于这个实例、且失败即意味着这个实例确实无法服务的东西:
- 启动已完成。配置解析完毕、必需的密钥齐全、所有每进程一次的初始化结束。
- 连接池已建立。
- 实例不在排空中。关闭时,就绪检查应当在服务器停止接受连接之前失败,这样负载均衡器会停止派活,而在途请求得以完成。跳过这一步,是本来干净的部署里出现错误的最常见原因。
排除那些所有实例共享的东西。如果一个依赖挂了,每个实例都会就绪失败、每个实例都会离开轮转,于是你把一次局部故障变成了全面故障 —— 而且往往顺手拿掉了那些本还能提供缓存或降级响应的实例。共享依赖的健康状况属于监控和告警,不属于控制路由的那个信号。
深度检查属于另一个 URL
有一个能验证整条依赖链的端点确实有价值 —— 部署期间、冒烟测试里、值班排查时。只是别让编排器拿它来做路由决策。
/healthz 存活 — 进程可用,不碰依赖
/readyz 就绪 — 本实例能服务,仅限实例级
/statusz 诊断 — 完整依赖细节,需鉴权,不用于路由
诊断端点应当报告每个依赖的状态和延迟,并且应当鉴权:它是一张关于你内部拓扑的精确地图。
两个值得点名的失败模式
什么都不检查的检查。 一个返回常量字符串的端点,或者更糟:一个由应用前面的反向代理直接提供的静态文件。它在后面的应用已经死掉时照样返回 200。如果你说不出你的健康检查能抓住哪一种故障,它就是装饰品。
过于聪明的检查。 一个自身做重活的就绪探针 —— 完整查询、缓存预热、外部调用 —— 会恰好在系统吃力时增加负载,而且它自己的超时会变成一种新的失败模式。健康检查一直在跑;它们必须便宜到在稳态下成本不可见。
可推广的那部分
看起来像网络问题的部署问题,经常是就绪问题。"服务起来了但头三十秒请求全失败"不是防火墙问题,是一个在尚未就绪时就报告就绪的实例。"部署期间请求失败"通常是一个在离开轮转之前就停止接受连接的实例。
两者同一个根:那个控制路由的信号,并没有真的在测量"路由过去会不会成功"。把这一个信号弄诚实 —— 实例级、便宜、启动中和排空中都为假 —— 就能消掉一整类原本极其难查的间歇性部署失败。