/health不能只返回200 OK,因其需区分进程存活与业务就绪:/health/live仅做轻量快照(如goroutine数、内存),而/health/ready必须同步探测DB/Redis/gRPC等依赖,任一失败即返回503并带具体错误项。

为什么/health不能只返回200 OK
很多 Go 服务在 Kubernetes 中频繁重启或流量涌入后立刻 500,根源就是把 /health 当成 /ping——只检查进程是否存活,不验证真实依赖是否就绪。Kubelet 看到 HTTP 200 就认为“可接收流量”,结果请求一进来就卡在 DB 连接超时或 Redis Ping() 失败上。
必须区分两个语义:
-
/health/live(或/healthz):只做轻量快照,比如runtime.NumGoroutine()是否异常、关键 channel 是否已 close、内存是否接近 cgroup limit -
/health/ready(或/readyz):必须同步探测所有强依赖,DB 调db.PingContext(ctx, 2*time.Second),Redis 用client.Ping(ctx).Err(),gRPC 查conn.GetState() == connectivity.Ready
任一依赖失败,/health/ready 必须返回 503 Service Unavailable,响应体带具体失败项,例如 {"redis": "timeout"},不能只写 {"status": "down"}。
如何避免健康检查拖垮主服务
探针请求卡住会阻塞整个 HTTP server 的 goroutine 池,尤其在高并发下,一个慢检查可能让所有业务请求排队等待。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 用独立的
http.ServeMux注册健康端点,和业务路由完全隔离,防止中间件 panic 或日志 hook 拖慢探针 - 所有依赖检查必须带
context.WithTimeout(context.Background(), 1500*time.Millisecond),总耗时控制在 2s 内 - 非强依赖项(如监控上报、日志服务)改用异步检查 + 缓存结果,不要阻塞
/health/ready响应流 - 别在 handler 里
new redis.Client或sql.Open——复用服务启动时已初始化的全局 client,否则每次探针都建连接,fd 很快耗尽
Kubernetes 探针配置常见错误
再好的 Go 健康端点,配错 K8s probe 也等于白写。最典型的是两个探针指向同一个路径,或超时设得太短。
-
livenessProbe应该用/health/live,initialDelaySeconds: 15(给 gRPC client warmup 留时间),timeoutSeconds: 1,失败后直接重启容器 -
readinessProbe必须用/health/ready,initialDelaySeconds: 20(比 liveness 更长,确保依赖已连上),timeoutSeconds: 3,失败时从 Service Endpoint 列表剔除 - 务必设置
periodSeconds: 5和failureThreshold: 3,避免网络抖动误判;successThreshold保持默认 1 即可
Go 代码里怎么主动控制就绪状态
Kubernetes 不支持运行时动态修改探针行为,但你可以让 readinessProbe 返回非 200,从而让 Endpoint Controller 自动摘掉这个实例。
- 用原子变量控制 handler 行为:
var isReady int32 = 1,readinessHandler中判断if atomic.LoadInt32(&isReady) == 0就返回503 - SIGTERM 信号到来时,先
atomic.StoreInt32(&isReady, 0),等 drain 完正在处理的请求,再退出 - DB 连接断开时,不要静默重试——立即置
isReady = 0,并在恢复连接后手动重置,避免“假就绪”
真正难的不是写个 /health,而是让每个依赖的失败都能被探测、被表达、被消费。Kubernetes 只看状态码和超时,Go 代码得自己扛起“真实就绪”的定义权。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










