根本原因是未将 promhttp.Handler() 挂载到 HTTP 路由;必须显式注册路由(如 Gin 中用 gin.WrapH(promhttp.Handler())),且监听地址不可为 127.0.0.1。

为什么 curl /metrics 返回 404 或空响应
根本原因就一个:没把 promhttp.Handler() 挂到 HTTP 路由上。只调 prometheus.MustRegister() 完全没用,指标再全也出不来。
常见错误场景:
- 注册了
httpReqCounter,但忘了http.Handle("/metrics", promhttp.Handler()) - 用了 Gin,却直接写
r.GET("/metrics", func(c *gin.Context) { ... }),没包装promhttp.Handler() - 路径写了
/monitor/metrics,但 Prometheus 配置里metrics_path还是默认的/metrics
正确做法(Gin):r.GET("/metrics", gin.WrapH(promhttp.Handler()))。注意必须在 r.Run() 之前执行,且监听地址不能是 127.0.0.1(外部 Prometheus 抓不到)。
自定义指标注册 panic: “duplicate metrics collector registration attempted”
这个 panic 几乎都来自重复注册同名指标,Go 的 Prometheus 客户端对此零容忍——不是报错,是直接 crash。
立即学习“go语言免费学习笔记(深入)”;
高频踩坑点:
- 把
prometheus.NewCounterVec()写在 handler 函数里,每次请求都新建+注册 - 多个
init()函数里各自调了MustRegister() - 用
promauto.NewCounterVec()时,变量没声明为包级变量,导致多次初始化
安全写法(推荐):var httpRequestsTotal = prometheus.NewCounterVec(prometheus.CounterOpts{Name: "http_requests_total"}, []string{"method", "path", "status_code"})
然后在 func init() { prometheus.MustRegister(httpRequestsTotal) } 里注册一次。所有打点都用 httpRequestsTotal.WithLabelValues("GET", "/api/user", "200").Inc()。
Gin 中记录请求耗时该用 Histogram 还是 Gauge
必须用 Histogram,Gauge 是错的。Gauge 只能存一个瞬时值,没法统计分布;而 P95/P99/平均延迟这些核心指标,依赖直方图的桶(bucket)和 _sum/_count 原始数据。
关键细节:
- 别用默认
prometheus.DefBuckets,它对 Web API 延迟太宽泛。建议按实际压测结果设:[]float64{0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5}(单位秒) - 指标名必须带
_bucket后缀,PromQL 的histogram_quantile()才能识别 - 打点时用
Observe(float64(duration.Seconds())),别用Set()或Inc() - 如果用
promauto,记得传prometheus.HistogramOpts{...},不是CounterOpts
活跃连接数、在线用户数为什么不能用 Counter
因为 Counter 不支持减操作。goroutine 退出时没法扣减,指标只会一路狂涨,完全失真。
正确选择是 Gauge,但必须配对使用:
- 连接建立时调
activeConnections.Inc() - 连接关闭时(比如 defer 或 context.Done() 后)必须调
activeConnections.Dec() - 如果用 WebSocket 或长连接,确保每个 goroutine 退出前都 Dec,否则泄漏会持续污染指标
更稳的做法:用 GaugeVec 按服务名或实例维度拆分,避免单个 Gauge 成为瓶颈。标签名严格小写+下划线,如 service_name,别用 serviceName 或 service-name。


















