90%场景下不应手写采集逻辑,应直接使用prometheus.register+promhttp.handler;自定义collector须正确实现describe()返回唯一desc,避免重复register;指标必须通过newgaugevec等工厂函数创建。

用 prometheus.ClientGatherer 替代手写采集逻辑
Go 服务暴露指标,90% 场景下不该从零实现采集器。直接复用 Prometheus 官方生态的 prometheus.Register + promhttp.Handler 就够用,自己写 Collect() 方法容易漏掉 Describe() 实现、并发安全或指标生命周期管理。
常见错误是把业务数据结构硬塞进 prometheus.Collector 接口,结果指标注册失败,日志里只报 "descriptor Desc{...} is invalid: duplicate metrics collector" —— 实际是没做 Describe() 返回唯一 prometheus.Desc,或重复调用 Register()。
- 所有自定义指标必须通过
prometheus.NewGaugeVec/NewCounterVec等工厂函数创建,它们内部已处理描述符和并发安全 - 若真需自定义采集(如拉取第三方 API 数据),
Collect()方法里必须用ch 发送,不能直接修改指标值 - 避免在
Collect()中做网络请求或阻塞操作,超时会拖垮整个/metrics端点
避免在 HTTP handler 里实时计算指标
有人习惯在 http.HandleFunc("/api/user", ...) 里直接 userCounter.Inc(),这没问题;但若在里面调 db.QueryRow("SELECT COUNT(*) FROM users") 再设 totalUsers.Set(float64(count)),就踩坑了。
问题在于:Prometheus 是拉模式,/metrics 被抓取时才触发采集。你把耗时操作塞进 handler,等于让每次 API 请求都承担指标上报开销,且无法对齐采集周期。
- 计数类指标(
Counter,Gauge)应在业务逻辑中更新,不是在采集时算 - 需要定期拉取的外部状态(如 Redis 连接数、磁盘剩余),应启独立 goroutine 按固定间隔(如 15s)更新指标,用
time.Ticker控制节奏 - 别用
http.HandlerFunc包裹指标采集逻辑——用promhttp.Handler()即可,它已内置线程安全与缓存
prometheus.Unregister() 不等于内存释放
热重载配置或动态启用/禁用某类指标时,常调 prometheus.Unregister(myCollector),以为能彻底清理。实际上它只是从全局 registry 移除引用,原 collector 实例仍驻留内存,且若该 collector 内部持有 channel、timer 或 goroutine,会持续泄漏。
典型表现:服务运行几小时后 goroutine 数缓慢上涨,pprof 显示一堆 runtime.gopark 卡在 collector 的 ticker 上。
- 调
Unregister()前,必须显式关闭 collector 自己启动的所有后台资源(stopCh 、<code>ticker.Stop()) - 如果 collector 是闭包构造的(如
func() prometheus.Collector { ... }),确保闭包捕获的变量不构成循环引用 - 更稳妥的做法是避免运行时 unregister —— 启动时按需注册,靠配置开关控制
Collect()是否发送指标(返回空即可)
区分 Summary 和 Histogram 的真实用途
看到“要统计请求耗时”,第一反应常是上 prometheus.NewSummary。但 Summary 在服务端不做分位数计算,全靠客户端采样+滑动窗口估算,一旦 Go 进程重启,历史 quantile 就清零;而 Histogram 是服务端聚合,更适合监控 P95/P99。
错误用法:用 Summary 暴露数据库查询延迟,结果 Grafana 里 P99 曲线毛刺严重,且重启后归零——这不是数据不准,是设计如此。
- 用
Histogram:需要稳定、可聚合的分位数(如 API 响应时间、RPC 延迟),尤其跨实例求平均 P95 - 用
Summary:仅当必须客户端精确计算(如单次请求内统计子步骤耗时分布),且能接受重启丢失 - 注意
Histogram的Buckets设置:太宽([]float64{0.1, 0.2, 0.5, 1.0, 2.0})导致 P99 落在最大桶里无法区分;太密(0.01步长)则指标量爆炸
实际部署时最易被忽略的是:指标命名空间(namespace)和子系统(subsystem)没按规范隔离,导致不同模块的 http_requests_total 冲突;还有就是忘记给 prometheus.GaugeVec 的 labels 加 constLabels 来固化服务元信息,结果每个实例上报的指标 label 不一致,PromQL 聚合失效。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











