go服务健康检查需解耦路由与状态逻辑,/health应检查核心依赖(如db、redis)是否在超时内可用,返回503而非500,区分liveness(进程存活)与readiness(业务就绪),避免假健康。

Go 服务的健康检查不需要框架也能写得稳,关键是把 /health 路由和状态判断逻辑解耦,避免误判“活着”的假象。
为什么 http.HandleFunc("/health", ...) 容易出问题
直接注册一个空 handler 响应 200 OK 是最常见也最危险的做法——它只说明 HTTP 服务进程没挂,但数据库连不上、缓存超时、磁盘写满时,照样返回健康。
- 真实场景中,
/health应该反映核心依赖是否可用,比如db.PingContext()、redis.Ping()是否在超时内成功 - 别在 handler 里做耗时操作(如查表、调远程 API),否则探活请求本身变成压测
- Kubernetes 的
livenessProbe默认超时 1 秒,超过就重启,而默认http.Server.ReadTimeout是 0(无限制)
用 http.ServeMux + context.WithTimeout 控制探活边界
健康检查必须有明确的超时和失败兜底,不能让一次慢查询拖垮整个探活链路。
func healthHandler(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 800*time.Millisecond)
defer cancel()
err := db.PingContext(ctx)
if err != nil {
http.Error(w, "db unreachable", http.StatusServiceUnavailable)
return
}
w.WriteHeader(http.StatusOK)
w.Write([]byte("ok"))
}
}
// 注册时带上依赖
mux := http.NewServeMux()
mux.HandleFunc("/health", healthHandler(db))
-
context.WithTimeout必须基于r.Context(),否则无法响应父级取消(如 K8s probe 中断) - 错误要返回
http.StatusServiceUnavailable(503),不是500;K8s 对 5xx 的处理策略不同,503 明确表示“暂时不可用” - 不要用
log.Print或fmt.Println打印健康日志——高频探活会刷爆日志,改用结构化日志并限流
liveness 和 readiness 别混在一起写
很多团队只留一个 /health,结果重启时新实例刚起来就立刻被流量打垮——因为还没加载完配置或预热缓存。
-
/live只检查进程是否存活:能否响应 HTTP、goroutine 是否卡死(可加runtime.NumGoroutine() > 10000预警) -
/ready检查业务就绪:DB 连通、配置加载完成、本地缓存 warmup 完成、依赖服务返回正常 - K8s 中两者 probe 设置应不同:
livenessProbe.initialDelaySeconds: 10,readinessProbe.initialDelaySeconds: 3 - 别让
/ready返回 JSON 包含太多字段——探活是机器读的,不是给人看的,ok或not_ready就够
真正难的不是写个能返回 200 的接口,而是定义清楚“什么才算健康”。比如一个离线任务调度服务,/ready 应该检查任务队列是否可写入,而不是 DB 是否连得上;而一个实时风控服务,/live 可能还要检查 CPU 使用率是否持续 >90%。这些判断点一旦写错,运维同学半夜就会收到告警。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











