应使用 /healthz 做存活检查(仅验证 HTTP server 是否可响应),/readyz 做就绪检查(并发带超时探测依赖),并缓存结果、记录结构化日志与 Prometheus 指标。
用 http.HandleFunc 实现最简健康端点,但别只返回 200 OK
Go 微服务健康检查的起点不是框架,而是标准库——http.HandleFunc 足够轻量、可控,且完全避开中间件干扰。但很多人卡在第一步:写了个 /health 返回 {"status":"UP"} 和 200,结果 Kubernetes 频繁重启服务。
问题出在语义错配:/health 应只反映进程是否存活(如 goroutine 是否卡死、HTTP server 是否还在 accept 连接),**不能查数据库、Redis 或下游 HTTP 服务**。否则一次 DB 网络抖动就会触发 livenessProbe 误杀,造成雪崩。
- 正确做法:用
/healthz做 liveness,逻辑仅限于http.Server是否可响应、关键 channel 是否未阻塞、内存/协程数是否异常增长 - 必须设
Content-Type: application/json,响应体字段统一用小写status,值为"UP"或"DOWN",不要用healthy或is_ok - 避免任何耗时操作——哪怕只是
time.Now()+json.Marshal也应控制在 100ms 内;超时直接 panic 不如返回 503 更安全
依赖检查必须走 /readyz,且要用 context.WithTimeout 控制每个调用
真正决定“能不能收流量”的是就绪状态,它必须同步探测关键依赖,但绝不能串行等待、也不能无超时阻塞。
比如用 db.Ping() 而非 db.PingContext(ctx),一旦数据库连接池卡住,整个 /readyz 就 hang 死,K8s readinessProbe 会持续超时,最终把实例从 Service Endpoints 永久剔除。
- 每个依赖检查必须带独立上下文超时:
db推荐 500–1000ms,redis.Client.Ping控制在 200–500ms,下游 gRPC 服务不超过 1.5s - 用
errgroup.Group并发执行所有检查项,任一失败即整体返回 503;不要等全部跑完再聚合 - 检查器应抽象为
type Checker func() (string, error),方便测试、开关和日志打标(例如 zap 记录component="redis" error="i/o timeout")
高频探活下,别每次请求都真实检查——用后台 goroutine 缓存结果
Kubernetes 默认每 10 秒调一次 /readyz,若每次都在 handler 里执行 db.PingContext + redis.Ping + http.Get,不仅增加延迟,还会对下游造成脉冲压力。