必须显式配置Buckets,否则观测值全入+Inf桶导致histogram_quantile失效;Go中prometheus.HistogramOpts不接受nil或空Buckets,需传递增float64切片并注意单位换算(毫秒/1000、纳秒/1e9);禁用new直构造,推荐promauto.NewHistogramVec;Observe应传time.Since(start).Seconds(),避免defer误用;查分位数须用histogram_quantile(rate(..._bucket[1m])),注意label收敛防基数爆炸,且结果为线性插值估算值。

必须显式配置 Buckets,否则所有观测值都挤进 +Inf 桶,histogram_quantile 查不出任何有效分位数。
Go 里创建 Histogram 必须传 Buckets,空切片或不设都会崩
Go 的 prometheus.HistogramOpts 不接受 nil 或空 Buckets。漏掉这个字段,prometheus.NewHistogram 会用默认的 DefBuckets(0.005~10 秒),但多数服务延时不匹配——比如内部 RPC 延时集中在 10~50ms,用默认桶会导致前几个桶全为 0,后半段全堆在 le="0.005" 和 le="+Inf" 之间。
-
Buckets必须是递增的[]float64,单位是秒;原始耗时是毫秒要除以 1000,纳秒要除以 1e9 - 别直接
new prometheus.Histogram:结构体字段未导出,运行时 panic 报错descriptor is inconsistent - 推荐写法:
promauto.NewHistogramVec+ 显式Buckets,避免手动Register导致重复注册 panic
histogram.Observe() 的单位和时机容易出错
延迟打点不是“记录开始和结束时间差”就完事。常见错误是把 time.Since(start) 直接传给 Observe,结果单位是纳秒,数值大出三个数量级;或者 defer 放在 handler 最外层,却没考虑 panic 或 context cancel 导致打点丢失。
-
Observe()只接受float64,推荐写法:h.Observe(time.Since(start).Seconds()) - 如果 handler 包含中间件(如 Gin 的 logger、auth),务必只在业务逻辑执行前后打点,排除框架开销
- 避免用
defer h.Observe(...)在函数入口,改用显式结束位置,或封装成带 recover 的打点辅助函数
查分位数要用 histogram_quantile + rate,但别误解它的行为
histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[1m])) 是标准写法,但很多人担心 rate 求斜率会“扭曲”分布。其实不会——histogram_quantile 只关心每个 _bucket{le="x"} 样本的相对大小关系,而 rate 输出的是每秒增量,仍保持各桶之间的累积比例不变。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
立即学习“go语言免费学习笔记(深入)”;
- 必须用
_bucket后缀指标,不能用_sum或_count;le标签必须存在,否则被忽略 - 时间窗口(如
[1m])要足够覆盖至少几十次请求,否则样本太少,P99 可能跳变剧烈甚至返回 0 - 如果服务流量极低(比如每分钟只有几次请求),改用
histogram_quantile(0.99, http_request_duration_seconds_bucket)(瞬时向量),但注意它反映的是最近一次抓取周期内的分布
加 label 要用 HistogramVec,动态 label 是陷阱
想按 method、path、status 看分位数?不能手动生成一堆不同名字的 Histogram,得用 prometheus.NewHistogramVec。但 label 值本身有风险:比如 path="/user/123" 这种带 ID 的路径,会引发基数爆炸,内存暴涨甚至 OOM。
- label 名称要收敛:用
path="/user/{id}"替代真实 ID,靠路由框架提取模板化路径 - 高频变动 label(如 trace_id、request_id)绝对禁止加入 HistogramVec
- 测试时记得调用
prometheus.Unregister()清理,但别在 Prometheus 正在 scrape 时调用——它不是 goroutine 安全的
最常被忽略的一点:histogram_quantile 返回的是估算值,不是精确数学分位数。它基于线性插值,在相邻两个 bucket 之间做假设,如果真实分布跨度过大(比如 P99 落在 le="1" 和 le="10" 之间),结果可能偏保守。真要高精度,得靠 Summary 类型,但代价是服务端无法聚合——这点在微服务多实例场景下很关键。

















