必须显式调用 prometheus.MustRegister() 且仅一次,否则指标未注册导致 /metrics 为空或 404;自定义 Registry 需配 promhttp.HandlerFor(reg, ...),注册须在 ListenAndServe 前完成。

用 prometheus/client_golang 暴露指标是 Go 服务监控的事实标准,自己手写计数器、拼 metrics 文本、绕过注册机制——全都会在上线后出问题。
怎么注册指标才不会 404 或返回空?
指标没注册 = Prometheus 看不见,promhttp.Handler() 只读默认注册器(prometheus.DefaultRegisterer),但很多人误以为“定义了变量就自动生效”。
- 必须显式调用
prometheus.MustRegister(),且只能调一次;重复注册会 panic:duplicate metrics collector registration attempted - 如果用了自定义
prometheus.NewRegistry(),就得配promhttp.HandlerFor(reg, ...),否则promhttp.Handler()还是查默认注册器,结果就是 /metrics 返回 200 但内容为空 - 注册必须在
http.ListenAndServe()之前完成,放在init()或main()开头最安全
Counter 和 Gauge 到底该用哪个?
类型选错不是“不好看”,而是会让 PromQL 查询直接失效,比如用 Gauge.Set() 记请求总数,服务重启后数值归零,rate() 就崩掉。
-
Counter:只增不减,适合累计量——http_requests_total、errors_total -
Gauge:可升可降,适合瞬时值——go_goroutines、cache_size_bytes - 别用
Gauge模拟 P95 延迟,那是Histogram的职责;也别用Counter记当前活跃连接数,它不会减
HTTP 中间件埋点为什么耗时不准?
常见写法是在 handler 开头打点、结尾再打点,但中间可能 panic、提前 return、或 defer 没覆盖所有出口,导致 Observe() 漏调或时机错位。
立即学习“go语言免费学习笔记(深入)”;
- 正确做法:用
defer包裹histogram.Observe(),且确保它在 handler 函数末尾执行 - 别在中间件里直接
Inc(),要等整个 handler 执行完再统计耗时和状态码,否则status_code标签可能拿不到真实值 - 标签值(如
path)要脱敏和泛化,/user/123得转成/user/{id},否则 label 基数爆炸,Prometheus 内存飙升
直方图 buckets 设置不当会怎样?
Histogram 不是画图工具,它是按预设边界分桶计数的。bucket 设得太宽,P95 算不准;太密或太多,每组 label 都生成一堆时间序列,撑爆 Prometheus。
- Web API 延迟建议用:
[]float64{0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10},比默认DefBuckets更贴合实际 - 如果发现
rate(http_request_duration_seconds_bucket[5m])全堆在最大 bucket(比如全是10),说明大部分请求超时了,得调大上限或查下游依赖 - 别在热路径里反复调
WithLabelValues(),它内部有 map 查找,高频场景应缓存好带 label 的子指标实例
最难的不是写对第一行 MustRegister,而是所有 handler 里都保持 label 语义一致、所有直方图都按业务 P99 预估分桶、所有 Counter 都不被意外重置——这些细节不在代码高亮里,但在告警电话响起时,全在日志和曲线背后。


















