根本原因是自定义指标http_requests_total未显式调用prometheus.mustregister()注册到defaultregisterer,导致/metrics仅暴露自动注册的运行时指标;业务指标必须在init()或main()开头提前注册,且不可重复或延迟至handler中注册。

为什么/metrics返回200却查不到http_requests_total?
根本原因不是端点没通,而是你定义的http_requests_total压根没注册进 Prometheus 的收集器。只写http.Handle("/metrics", promhttp.Handler()),暴露的只有 Go 运行时指标(如go_goroutines、go_memstats_alloc_bytes),业务指标必须显式注册。
常见错误写法:
- 把
prometheus.MustRegister()放在 HTTP handler 里、中间件里,或http.ListenAndServe()之后 - 用
promauto.NewCounterVec()但没控制包导入顺序,导致多个init()重复注册同名指标,启动 panic - 误以为“挂了路由就等于指标就绪”,忽略注册时机和注册器归属
CounterVec 和 Histogram 怎么选、怎么配才不翻车?
SLA 要求成功率 + P99 延迟,这两类指标必须分开建、分开用,混用会查不准甚至查不出结果。
http_requests_total必须是CounterVec,带method、endpoint、status标签 —— 这样才能算成功率:rate(http_requests_total{status!="200"}[5m]) / rate(http_requests_total[5m])
http_request_duration_seconds必须是Histogram(不是Summary),因为Summary不支持rate()和跨时间窗口聚合,P99 会严重失真。
关键细节:
-
Histogram的Buckets要覆盖 SLA 目标,比如 SLA 是 200ms,推荐设[]float64{0.05, 0.1, 0.2, 0.5};默认桶从 10ms 开始,对毫秒级服务基本无效 -
status不能塞进Histogram的 label —— 它不支持多维标签聚合,状态统计交给CounterVec,延迟分布交给Histogram
如何避免标签爆炸导致 Prometheus 存储崩掉?
高基数标签(如user_id、trace_id、原始路径/order/12345)会让指标数量指数级增长,轻则查询超时,重则 OOM 崩溃。
实操建议:
- 路径类标签统一做归一化:把
/order/12345→/order/{id},用正则或中间件预处理 - 绝对禁止将用户标识、设备 ID、请求 ID 作为 label;需要关联时,走日志+traceID 关联,而非指标打标
- 上线前用
curl -s http://localhost:8080/metrics | grep -E 'http_requests_total\{.*\}' | head -20快速检查 label 组合是否失控
中大型项目怎么安全注册指标不 panic?
多个包都 import 了定义指标的工具模块,init()里各自调MustRegister(),必然触发panic: duplicate metrics collector registration。
这不是并发问题,是注册逻辑污染了全局prometheus.DefaultRegisterer。
可靠做法:
- 所有指标实例(
*prometheus.CounterVec、*prometheus.Histogram)只在main()或服务初始化入口创建并注册一次 - 其他模块通过参数注入指标对象,不碰
MustRegister——例如 HTTP handler 只调counter.WithLabelValues(...).Inc() - 更彻底的隔离方案:用
prometheus.NewRegistry()替代默认注册器,再传给promhttp.HandlerFor(reg, promhttp.HandlerOpts{})
真正难的不是写指标代码,而是让指标在千级 QPS 下稳定注册、不冲突、不爆炸、不误导排查——所有这些,都在注册那一行代码的前后几行里定生死。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











