根本原因是未调用prometheus.MustRegister()注册自定义指标,仅挂载/metrics路由只能暴露Go运行时指标;必须在http.ListenAndServe()前完成注册,且标签名、顺序、值需严格一致,否则指标静默丢失。

Go 微服务暴露 Prometheus 指标,不是配个 /metrics 路由就完事——漏掉注册、标签乱用、类型选错,指标要么为空、要么爆炸、要么查不到 P99。
为什么 /metrics 返回 200 却没业务指标?
根本原因:只挂了 promhttp.Handler(),但没调 prometheus.MustRegister()。默认只暴露 Go 运行时指标(如 go_goroutines),你定义的 http_requests_total 这类变量只是内存对象,不注册就等于不存在。
- 必须在
http.ListenAndServe()之前完成注册,常见错误是把MustRegister写在 handler 里或中间件里 - 运行时指标也要显式注册:
prometheus.MustRegister(prometheus.NewGoCollector()),否则go_memstats_alloc_bytes等不会出现 - 用
promauto.NewCounterVec()时,它内部调了MustRegister,但多个包重复初始化同名指标会 panic;中大型项目建议统一用prometheus.NewRegistry()+ 手动reg.MustRegister()
HTTP 请求指标该用 CounterVec 还是 Histogram?
SLA 关注成功率和耗时达标率,两类指标缺一不可,且不能混用:
-
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
标签设计怎么避免指标基数爆炸?
高基数标签(如用户 ID、trace ID、原始路径 /order/12345)会让 Prometheus 存储和查询迅速崩掉。
立即学习“go语言免费学习笔记(深入)”;
- 路径必须归一化:
/order/12345→/order/{id},否则每个订单生成独立时间序列 - 禁止直接用业务 ID 类字段打标;改用分类统计,比如按
error_type(timeout、db_error、network)分组计数 - 标签名全小写+下划线,如
tenant_id,不能写TenantID或tenant-id -
WithLabelValues()传参顺序必须和NewCounterVec(..., []string{"a","b","c"})定义完全一致,少一个、空字符串、顺序错,指标静默丢失
Prometheus 抓不到指标,先查这三件事
90% 的 “Target UP 但查不到数据” 问题,跟 Go 代码无关,是基础设施层硬性约束没满足。
- 在 Prometheus 所在机器上执行:
curl -v http://your-go-service:9091/metrics,连不通就不是配置问题,是监听地址错了(比如 Go 服务绑了127.0.0.1:9091,外部访问不了) - Docker 场景下,
scrape_configs.targets别写localhost:9091;Mac/Win 用host.docker.internal:9091,Linux 用宿主机局域网 IP - 响应头必须含
Content-Type: text/plain; version=0.0.4——promhttp.Handler()默认满足,但若自己封装 handler 或用 Gin 时手动写响应,容易漏掉
最易被忽略的是:注册时机和标签一致性。指标一旦注册就固定维度,运行时不能增删 label;而 WithLabelValues 的参数错误不会报错,只会让那条指标永远消失——它不像日志,丢了还能猜,指标丢了就是查无此数。


















