Go服务对接Prometheus的核心是确保/metrics端点稳定合规,只需两步:导入promhttp并挂载到路由;指标注册必须在init()中完成,避免运行时注册导致抓取失败;业务指标优先用CounterVec但需控制label基数,Histogram应自定义合理buckets。

Go 服务对接 Prometheus 的核心不是“写一堆指标”,而是让 /metrics 端点稳定、合规、可被正确抓取——只要这个端点返回符合 Prometheus 文本格式的指标,且标签控制得当,采集就基本成功。
如何注册并暴露 /metrics 端点(最简可行路径)
不需要自己拼字符串、不需手写 HTTP handler。官方 promhttp.Handler() 已封装全部逻辑,只需两步:
- 导入
"github.com/prometheus/client_golang/prometheus/promhttp" - 在 HTTP 路由中挂载:
http.Handle("/metrics", promhttp.Handler())
启动后访问 http://localhost:8080/metrics 就能看到默认运行时指标(如 go_goroutines、process_cpu_seconds_total)。注意:这个 handler 默认使用全局注册表(prometheus.DefaultRegisterer),所有后续注册的指标都会自动生效。
为什么定义指标必须在 init() 或程序启动早期?
Prometheus 抓取是定时拉取,如果指标变量声明了但没注册,或注册发生在 handler 第一次调用之后,那么首次抓取时该指标根本不存在——Prometheus 不会报错,只会静默忽略,你查不到任何数据,也看不到错误日志。
立即学习“go语言免费学习笔记(深入)”;
- 注册必须早于
http.ListenAndServe启动 - 推荐统一放在
func init()中,确保顺序确定 - 避免在某个 handler 里才调用
prometheus.MustRegister(...),这是常见误操作
例如下面这段代码会导致指标永远不被采集:
func handler(w http.ResponseWriter, r *http.Request) {
// ❌ 错误:每次请求才注册,第一次抓取时指标还不存在
prometheus.MustRegister(httpRequestsTotal)
httpRequestsTotal.Inc()
}
用 CounterVec 还是 Counter?标签设计的关键陷阱
业务指标几乎都该用 CounterVec(带 label 的向量),而不是裸 Counter。但 label 值必须可控,否则会引发高基数(high cardinality)问题——比如把用户 ID、订单号、原始 URL 路径直接当 label 值,可能导致 Prometheus 内存暴涨甚至 OOM。
- ✅ 推荐:
r.Method、status_code、归一化后的r.URL.Path(如用正则替换/user/123→/user/{id}) - ❌ 危险:
r.RemoteAddr、r.Header.Get("X-Request-ID")、未处理的r.URL.Path - 验证方式:抓取一次
/metrics,搜索你的指标名,看_total{...}后面的 label 组合是否稳定、数量是否在几十以内
示例中这样注册和使用才是安全的:
var httpRequestsTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total number of HTTP requests",
},
[]string{"method", "status_code", "path_group"}, // path_group 是归一化后的路径分组
)
func init() {
prometheus.MustRegister(httpRequestsTotal)
}
// 在中间件中:
pathGroup := normalizePath(r.URL.Path) // 自定义归一化函数
httpRequestsTotal.WithLabelValues(r.Method, strconv.Itoa(status), pathGroup).Inc()
直方图 Histogram 比手动计时更可靠,但别乱设 Buckets
用 Histogram 记录耗时,本质是自动做分桶统计 + 提供 _sum / _count / _bucket 三组指标,方便算平均值、P95、QPS 等。但它不是万能的:
-
Buckets应覆盖你服务的真实延迟分布,不要照搬默认DefBuckets(最大只到 10 秒)——如果你的 API 大部分在 200ms 内完成,却用[0.005, 0.01, ..., 10],会导致前几个 bucket 密集打点、后面大量空桶浪费内存 - 更推荐自定义紧凑 bucket,例如:
[]float64{0.05, 0.1, 0.2, 0.5, 1.0, 2.0}(单位秒) - 避免在每个 handler 里 new 一个
Histogram,应复用全局变量 +WithLabelValues
最简可靠的用法是配合 prometheus.NewTimer:
func handler(w http.ResponseWriter, r *http.Request) {
timer := prometheus.NewTimer(httpRequestDuration.WithLabelValues(r.Method, normalizePath(r.URL.Path)))
defer timer.ObserveDuration()
// ...业务逻辑
}
真正卡住监控落地的,往往不是不会写指标,而是没意识到 label 基数失控、注册时机错乱、bucket 设置脱离实际——这些细节不排查,再全的指标也等于没有。










