go http服务应提供/healthz(仅检查进程与端口)和/readyz(检查依赖)两个轻量http端点;/healthz须返回200,/readyz中db检查应避免实时db.ping(),改用缓存最近成功时间并校验时效性。

Go HTTP 服务如何暴露标准健康检查端点
Kubernetes 的 livenessProbe 和 readinessProbe 本质就是发 HTTP GET 请求,只要你的 Go 服务跑在 HTTP server 上,就只需要提供两个稳定、低开销、无副作用的 HTTP handler。别写成带数据库查询或锁竞争的“假健康接口”。
推荐直接复用 http.ServeMux 或主流路由库(如 chi、gorilla/mux),注册两个路径:
-
/healthz用于 liveness:只检查进程是否存活、监听端口是否就绪 -
/readyz用于 readiness:额外检查依赖(DB 连接、下游 API、本地缓存加载状态等)
不要把二者混用,也不要让 /healthz 返回 500 来表示“不健康”——K8s 默认把非 2xx/3xx 当失败,但 5xx 会触发重启循环,而进程明明活着。应统一返回 200 + 简洁 body,靠状态码区分生死。
Readiness probe 中 DB 连接检查怎么写才不拖垮服务
常见错误是每次 /readyz 请求都执行 db.Ping(),尤其当 DB 连接池空或网络抖动时,会卡住整个 probe 请求,导致 K8s 反复摘除实例,形成雪崩。
立即学习“go语言免费学习笔记(深入)”;
正确做法是只检查连接池是否可用、且最近一次 Ping 在 N 秒内成功过:
- 用
sync.Once或启动时预热连接,避免首次 probe 延迟高 - 用
time.Now().Sub(lastPingTime) 缓存结果,而非实时调用 <code>db.Ping() - 如果 DB 真断了,让下一次后台 goroutine 异步重试并更新
lastPingTime,probe 接口只读状态
示例逻辑片段:
var (
lastPingTime time.Time
pingMu sync.RWMutex
)
func checkDBReady() bool {
pingMu.RLock()
defer pingMu.RUnlock()
return time.Since(lastPingTime) < 30*time.Second
}
HTTP handler 里为什么不能用 log.Fatal 或 panic
因为 log.Fatal 会调用 os.Exit(1),整个进程退出;panic 若没被 recover,也会终止 goroutine 并可能让 HTTP server 关闭监听。K8s 看到探针请求超时或连接拒绝,立刻判定容器死亡,触发重启——但你本意只是想标记“暂时不可用”。
必须保证 handler 函数始终返回,哪怕出错也返回 503 或 200 带明确 message:
- 所有外部调用(DB、Redis、HTTP client)加 context.WithTimeout,防止阻塞
- 错误只记录日志,不中断流程:
log.Printf("warn: redis ping failed: %v", err) - 最终响应体建议包含关键依赖状态,方便人工排查:
{"status":"ok","db":"ok","redis":"timeout"}
Golang net/http 默认超时对 probe 的隐性影响
Go 的 http.Server 默认没有设置 ReadTimeout、WriteTimeout,一旦某个 handler 卡死(比如忘了设 context timeout 的 DB 查询),整个 server 可能持续占用连接,probe 请求堆积,K8s 最终因超时判定失败。
必须显式配置 server 超时,尤其 probe 端点要更激进:
-
ReadTimeout: 5 * time.Second—— 防止恶意大 body 拖慢 -
WriteTimeout: 5 * time.Second—— 确保 probe 响应不卡住 - 对
/healthz和/readyzhandler 单独加更短的 context:ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)
别依赖 K8s probe 的 timeoutSeconds 字段兜底——那是请求层面的超时,挡不住 Go runtime 层面的 goroutine 泄漏。
最易被忽略的一点: readiness probe 不仅决定流量是否打入,还影响滚动更新时旧 Pod 的销毁时机。如果 /readyz 因缓存未加载完一直返回失败,新 Pod 启不来,旧 Pod 又等不到下线信号,升级就卡住。所以 readiness 检查项必须可收敛、有明确完成态,不能依赖“等待某事发生”。










