一句话说明
/health 恒返回 ok,/ready 只看 kernel state 是不是 running —— 两者都从不碰 data driver。数据库在启动之后掉线,探针照样绿,负载均衡不摘这个副本、编排器不重启它,而它服务的每个请求都 500。
#3741 / #3751 修掉了这个问题的启动期版本(连不上就拒绝启动)。运行期版本还在。
事实核实
packages/runtime/src/http-dispatcher.ts:283-296 —— /health:
this.domainRegistry.register({
prefix: '/health', match: 'exact', methods: ['GET'],
handler: async () => ({
handled: true,
response: this.success({
status: 'ok',
timestamp: new Date().toISOString(),
version: '1.0.0',
uptime: typeof process !== 'undefined' ? process.uptime() : undefined,
}),
}),
});
同文件 :302-311 —— /ready:
handler: async () => {
const state: string = typeof (this.kernel as any)?.getState === 'function'
? (this.kernel as any).getState()
: 'running';
return state === 'running'
? { handled: true, response: this.success({ status: 'ready', state }) }
: { handled: true, response: this.error('Service not ready', 503, { state }) };
},
三点:
- 两个探针都没有任何数据层依赖。
/ready 的 state === 'running' 在 kernel bootstrap 完成的那一刻置位,此后除非进入 stopping/stopped 否则永不改变 —— 它回答的是"kernel 生命周期到哪一步了",不是"这个副本现在能不能服务请求"。
- driver 的
checkHealth() 早就存在且很便宜(driver-sql 是 SELECT 1,driver-mongodb 是 db.command({ping:1})),但只被 datasource-admin 的 testConnection 消费(datasource-admin-plugin.ts:466)。没有任何探针路径调用它。
ObjectQL 引擎根本没有健康检查面。 drivers 是 private drivers = new Map<...>()(engine.ts:326),没有 getter,没有聚合的 health 方法 —— 就算探针想问也没地方问。
PluginHealthMonitor(packages/core/src/health-monitor.ts)管的是 plugin,不覆盖 driver 连接。
影响
数据库在进程启动后不可用(重启、failover、网络策略变更、连接池耗尽、凭据轮换):
这跟 #3741 是同一个病的另一面:声称在报告健康,实际没检查那个唯一重要的依赖。
建议处置
关键是不要把两个探针混为一谈 —— 它们的正确答案是相反的:
/health 保持不依赖数据库,并把这一点写进注释。 liveness 探测失败 → 编排器重启 pod。重启一个 pod 不会修好宕掉的数据库,只会让所有副本在数据库抖动期间陷入重启风暴,同时销毁掉本可以正常服务的 in-flight 请求。liveness 只该回答"这个进程还活着吗"。现状是对的,缺的是说明它为什么对,否则下一个人会"顺手"把 DB 检查加进去。
/ready 检查 data driver。 readiness 探测失败 → 把副本摘出 LB 轮转,不重启。一个连不上数据库的副本 100% 的请求都会失败,摘掉它正是 readiness 存在的意义。具体:
- 给
ObjectQL 加一个公开的聚合健康检查(drivers 目前是 private,外部无从问起)。
/ready 特性探测地消费它 —— 没有 data engine / 没有该方法(lite kernel、edge)就退回现有行为,不能因此把这些运行时判成 not-ready。
- 必须带超时。
checkHealth() 本身吞异常返回 false,但在一个死掉的连接池上,knex 的 SELECT 1 会一直等到 acquireConnectionTimeout(默认 60s)。一个挂住的探针和一个撒谎的探针一样糟 —— 超时即判 not-ready。
- 必须有短 TTL 缓存。 k8s 默认几秒一次探测 × 每个副本,不能每次都打一个 DB 往返。1 秒左右的 memo 就够,且不会让判定明显滞后。
关联
一句话说明
/health恒返回ok,/ready只看 kernel state 是不是running—— 两者都从不碰 data driver。数据库在启动之后掉线,探针照样绿,负载均衡不摘这个副本、编排器不重启它,而它服务的每个请求都 500。#3741 / #3751 修掉了这个问题的启动期版本(连不上就拒绝启动)。运行期版本还在。
事实核实
packages/runtime/src/http-dispatcher.ts:283-296——/health:同文件
:302-311——/ready:三点:
/ready的state === 'running'在 kernel bootstrap 完成的那一刻置位,此后除非进入stopping/stopped否则永不改变 —— 它回答的是"kernel 生命周期到哪一步了",不是"这个副本现在能不能服务请求"。checkHealth()早就存在且很便宜(driver-sql是SELECT 1,driver-mongodb是db.command({ping:1})),但只被datasource-admin的testConnection消费(datasource-admin-plugin.ts:466)。没有任何探针路径调用它。ObjectQL引擎根本没有健康检查面。drivers是private drivers = new Map<...>()(engine.ts:326),没有 getter,没有聚合的 health 方法 —— 就算探针想问也没地方问。PluginHealthMonitor(packages/core/src/health-monitor.ts)管的是 plugin,不覆盖 driver 连接。影响
数据库在进程启动后不可用(重启、failover、网络策略变更、连接池耗尽、凭据轮换):
/health200、/ready200 → k8s 认为副本健康就绪,LB 继续打流量进来,而每个请求 500。这跟 #3741 是同一个病的另一面:声称在报告健康,实际没检查那个唯一重要的依赖。
建议处置
关键是不要把两个探针混为一谈 —— 它们的正确答案是相反的:
/health保持不依赖数据库,并把这一点写进注释。 liveness 探测失败 → 编排器重启 pod。重启一个 pod 不会修好宕掉的数据库,只会让所有副本在数据库抖动期间陷入重启风暴,同时销毁掉本可以正常服务的 in-flight 请求。liveness 只该回答"这个进程还活着吗"。现状是对的,缺的是说明它为什么对,否则下一个人会"顺手"把 DB 检查加进去。/ready检查 data driver。 readiness 探测失败 → 把副本摘出 LB 轮转,不重启。一个连不上数据库的副本 100% 的请求都会失败,摘掉它正是 readiness 存在的意义。具体:ObjectQL加一个公开的聚合健康检查(drivers目前是 private,外部无从问起)。/ready特性探测地消费它 —— 没有 data engine / 没有该方法(lite kernel、edge)就退回现有行为,不能因此把这些运行时判成 not-ready。checkHealth()本身吞异常返回false,但在一个死掉的连接池上,knex 的SELECT 1会一直等到acquireConnectionTimeout(默认 60s)。一个挂住的探针和一个撒谎的探针一样糟 —— 超时即判 not-ready。关联