Go服务应暴露/health端点,检查进程状态与关键依赖(如DB、Redis),返回200或503;需结合资源水位(如内存)与关键路径探活,避免仅依赖HTTP 200;同时集成Prometheus指标和panic防护机制。

Go 服务如何暴露健康检查端点(/health)
生产环境必须提供可被外部探测的健康状态接口,最常用的是 HTTP /health 端点。它不返回业务数据,只反映进程是否存活、关键依赖是否就绪。
用标准库即可实现,无需额外框架:
func healthHandler(w http.ResponseWriter, r *http.Request) {
// 检查本地状态:goroutine 数量、内存压力等(可选)
// 检查关键依赖:数据库 ping、Redis 连接、下游 HTTP 服务连通性
if err := db.Ping(); err != nil {
http.Error(w, "db unreachable", http.StatusServiceUnavailable)
return
}
if err := redisClient.Ping(r.Context()).Err(); err != nil {
http.Error(w, "redis unreachable", http.StatusServiceUnavailable)
return
}
w.WriteHeader(http.StatusOK)
w.Write([]byte("ok"))
}
注意:http.StatusServiceUnavailable(503)比 500 更准确,表示“暂时不可用”,适合监控系统自动降级或剔除流量。
为什么不能只靠 HTTP 200 判断服务正常
HTTP 200 只代表 handler 执行完没 panic,并不等于业务可用。常见陷阱包括:
立即学习“go语言免费学习笔记(深入)”;
-
db.Ping()成功但慢查询堆积,连接池耗尽 - Redis 连接正常,但
SET命令超时或返回MOVED重定向错误 - 依赖的 gRPC 服务响应 200,但实际返回了
status.Code == codes.Unavailable - 内存持续上涨,
runtime.ReadMemStats()显示HeapInuse接近上限
所以健康检查逻辑里必须包含「关键路径探活」+「资源水位快照」,例如:
var memStats runtime.MemStats
runtime.ReadMemStats(&memStats)
if memStats.HeapInuse > uint64(800*1024*1024) { // 超 800MB
http.Error(w, "high memory usage", http.StatusServiceUnavailable)
return
}
如何用 Prometheus + /metrics 暴露运行时指标
Prometheus 是 Go 服务最主流的指标采集方案,需引入 prometheus/client_golang 并注册指标:
import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var (
httpRequestsTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total number of HTTP requests",
},
[]string{"method", "status"},
)
)
func init() {
prometheus.MustRegister(httpRequestsTotal)
}
// 在 HTTP handler 中记录
httpRequestsTotal.WithLabelValues(r.Method, strconv.Itoa(statusCode)).Inc()
然后挂载 /metrics handler:
http.Handle("/metrics", promhttp.Handler())
关键点:promhttp.Handler() 默认只暴露 Go 运行时指标(goroutines、GC 次数等),业务指标需显式 MustRegister();且避免在请求中高频调用 WithLabelValues() 创建新指标实例,应复用。
进程级异常:如何捕获 panic 并上报
HTTP server 启动后发生的 panic(如 handler 内未处理的空指针)会导致整个服务退出。必须做两层防护:
- 全局
http.Server的ErrorLog配置,捕获底层监听错误 - 每个 handler 内用
defer/recover捕获 panic,并记录结构化日志
示例:
func safeHandler(fn http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
log.Printf("PANIC in %s %s: %v", r.Method, r.URL.Path, err)
// 上报到 Sentry / Datadog / 自建告警通道
reportPanic(err, r)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
}()
fn(w, r)
}
}
http.HandleFunc("/api/user", safeHandler(userHandler))
注意:recover() 只对当前 goroutine 有效,无法捕获其他 goroutine(如定时任务、消息消费协程)的 panic,这些需单独加 defer/recover。
真正难的是依赖链路的异步失败——比如一个 HTTP 请求触发了 Kafka 消息发送,而发送 goroutine panic 了,主请求却已返回 200。这种场景必须靠 end-to-end trace + 异步任务状态表 + 定期巡检来兜底。










