优先选 Histogram;Summary 仅适用于单实例、低频、高精度场景,因其分位数不可合并、内存占用高且不支持 rate() 等聚合函数。

Summary 和 Histogram 到底该选哪个
Summary 直接在客户端计算分位数,适合低频、高精度要求的场景;Histogram 把原始数据打点到服务端再聚合,适合高频、需要灵活切分标签的场景。Prometheus 官方文档明确建议:除非你确定需要客户端精确分位数,否则优先用 Histogram——因为 Summary 的 quantile 计算不可合并,多实例下 sum() 或 rate() 会失效,且内存占用随采样数线性增长。
常见错误现象:summary_quantile{quantile="0.95"} 在多个 Pod 上查出的值差异极大,且加了 sum by (quantile) (...) 后结果毫无意义。
- 使用场景:Summary 仅推荐用于单实例长期运行的服务(如配置中心、内部工具 API),或调试阶段快速看分位趋势
- 性能影响:每秒 100 次 observe(),
SummaryVec默认保留 1024 个样本,内存持续增长;可调小MaxAge和BufCap,但会牺牲精度 - 兼容性:不支持 Prometheus 的
histogram_quantile()函数,也不能用rate()做速率聚合
初始化 Summary 要传哪些参数才不翻车
Go SDK 的 prometheus.NewSummary 必须显式指定 Objectives,否则默认空 map,导致所有 quantile 标签都不上报——你查不到任何数据,也不会报错。
示例中常漏掉的点:
立即学习“go语言免费学习笔记(深入)”;
-
Objectives是 map[float64]float64,key 是 quantile(如 0.95),value 是最大误差(如 0.01)。别写成map[string]float64 -
MaxAge默认 10 分钟,如果请求稀疏,老样本没被覆盖就一直占内存;建议设为5 * time.Minute -
BufCap默认 500,高频写入时可能丢样本;压测中观察promhttp_metric_handler_requests_total{code="500"}是否突增
正确初始化片段:
reqDurSummary := prometheus.NewSummary(prometheus.SummaryOpts{
Name: "http_request_duration_seconds",
Help: "Latency distribution of HTTP requests",
Objectives: map[float64]float64{
0.5: 0.05,
0.9: 0.01,
0.95: 0.005,
0.99: 0.001,
},
MaxAge: 5 * time.Minute,
BufCap: 1000,
})observe() 调用时机和单位必须严格对齐
Summary 只存 float64,不会帮你转换单位。如果你用 time.Since(start).Milliseconds() 得到整数毫秒,再直接传给 observe(),那 100ms 就变成 100.0 秒——图表全飘到右上角,报警全炸。
真实踩坑点:
- 必须统一用秒(
Seconds())或统一用毫秒(Milliseconds()),并在指标 Help 和 Grafana 图表里明确标注单位 - HTTP 中间件里
observe()必须在 defer 之后、response 写完之后调用,否则 panic 时延时为 0 - 不要在 goroutine 里异步调用
observe():Summary 不是并发安全的,要用SummaryVec配合 label 区分上下文
典型错误写法:
// ❌ 单位错 + panic 时漏上报
func handler(w http.ResponseWriter, r *http.Request) {
start := time.Now()
defer func() { reqDurSummary.Observe(float64(time.Since(start).Milliseconds())) }() // 这里单位是毫秒,但指标名叫 _seconds
// ... 处理逻辑,可能 panic
}SummaryVec 的 label 值不能动态拼接
很多人想按 path 模板(如 /api/v1/users/:id)打 label,于是写 vec.WithLabelValues(r.URL.Path),结果 Prometheus 报 collected metric was invalid: duplicate label names,或者指标暴涨到几万 series,OOM。
根本原因:r.URL.Path 是任意用户输入,会产生无限 label 组合,违反 Prometheus cardinality 原则。
- 正确做法:预定义有限 label 值,比如用
route字段从 Gin Echo 等框架中提取,或用正则匹配后映射为固定字符串("user_get","user_list") - 避免用 query 参数、header、body 做 label;这些信息应进日志,而非指标
- 如果真要区分高频路径,改用 Histogram +
lebucket,它天然支持高基数聚合
label 命名也容易错:别用 path,用 route 或 endpoint,避免和内置 label 冲突。
最常被忽略的一点:Summary 的 quantile 样本不是实时刷新的,而是滑动窗口内估算。如果你只每分钟调用一次 observe(),那 0.99 分位数可能连续半小时都显示同一个值——它不是“最近一分钟的 99% 分位”,而是“过去五分钟内所有样本的估算 99% 分位”。要看真正滚动窗口,得自己用 promql 套 quantile_over_time(),但前提是用 Histogram。


















