/healthz 和 /livez 必须分离,否则 Kubernetes 会误杀服务;/livez 仅检查进程存活(如 HTTP 可接入、goroutine 数量、内存),不探查数据库等外部依赖,避免因依赖抖动触发重启雪崩。

/healthz 和 /livez 必须分离,否则 Kubernetes 会误杀服务。
为什么不能只用一个 /health 接口
很多团队上线后发现服务频繁重启,查日志全是 livenessProbe 失败——根本原因就是把数据库、Redis 连通性检查塞进了 /health(或 /healthz)里。Kubernetes 的 livenessProbe 触发的是容器重启,一旦依赖抖动,整个实例被拉起又杀死,形成雪崩循环。
正确做法是语义隔离:
-
/livez:只确认 HTTP server 是否在 accept 连接、关键 goroutine 是否卡死、内存是否暴涨(比如runtime.NumGoroutine()> 10000)。不碰任何外部依赖,响应必须 -
/healthz:同步探测所有关键依赖(DB、Redis、下游 gRPC),但每个调用都带独立context.WithTimeout,且用errgroup.Group并发执行
如何避免定时检查 goroutine 泄漏
用 time.Ticker 做后台健康巡检很常见,但线上最常踩的坑是:服务热重启时旧 ticker 没停,新 ticker 又起,多个 goroutine 并发写同一个状态变量,结果接口返回 "redis.ok": true,实际已断连。
立即学习“go语言免费学习笔记(深入)”;
必须做到两点:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 用
context.WithCancel包裹 ticker 循环,收到 shutdown 信号时主动ticker.Stop() - 健康状态变量(如
redisHealthy)必须用sync/atomic或sync.RWMutex保护,禁止裸写
HTTP 探测必须显式控制超时和重定向
直接用 http.Get 做依赖探测,线上十次有九次栽在默认行为上:没超时、自动跳转、不关 body。
实操要点:
- 构造专用
http.Client,Timeout设为 3–5 秒(例如time.Second * 4) - 设置
CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse },禁用自动重定向 - 每次调用后必须
resp.Body.Close(),否则短时间高频探测会耗尽文件描述符 - 别只看
err == nil,要校验resp.StatusCode是否在预期范围(200、204),并读取前 N 字节验证响应体有效性(防空 200)
依赖检查结果必须缓存,不能每次请求都真实探测
Kubernetes 默认每 10 秒调一次 /healthz,如果每次都在 handler 里执行 db.PingContext + redis.Ping + http.Get,不仅延迟高,还会对下游造成脉冲压力。
更合理的结构是:
- 启动一个后台 goroutine,用
time.Ticker每 5 秒执行一次全量依赖检查 - 将结果(含时间戳、各子项状态、错误信息)原子写入内存缓存(如
atomic.Value) -
/healthzhandler 只读快照并返回,不触发任何网络 I/O
这个缓存机制看似多一步,却是避免下游被打挂、保障探针稳定性的关键分水岭。

















