四种指标类型 · 以及怎么选

counter、gauge、histogram、summary —— 四种类型对应四种度量语义。这一章讲清各自适用什么、命名约定,并重点拆解那个最经典的实战决策:histogram 还是 summary(答案几乎总是 histogram,原因是可聚合性)。

作者 David更新于 第 8 篇(共 52 篇)

四种指标类型 · 以及怎么选

上一章说 series 的值就是个 float64。那“类型”是什么?类型是给人和查询看的语义约定——它决定了你该怎么查这个指标。选错类型,后面全是坑。

四种类型

看解析器认哪些类型(真实源码 model/textparse/promparse.go:337):

switch s := yoloString(p.text); s {
case "counter":   p.mtype = model.MetricTypeCounter
case "gauge":     p.mtype = model.MetricTypeGauge
case "histogram": p.mtype = model.MetricTypeHistogram
case "summary":   p.mtype = model.MetricTypeSummary
case "untyped":   p.mtype = model.MetricTypeUnknown
default:          return EntryInvalid, fmt.Errorf("invalid metric type %q", s)
}

四种类型,四种语义:

类型语义典型例子怎么查
counter只增不减(重启归零)请求总数、错误总数、发送字节数必须 rate()/increase()
gauge可增可减的瞬时值内存占用、队列长度、并发连接数直接看,或 avg/max
histogram分桶统计分布请求延迟、响应体大小histogram_quantile()
summary客户端算好的分位数同上(但慎用)直接读 quantile 标签

最重要的一条经验:类型不是“标注”,而是“承诺”。 你把一个会下降的值标成 counter,rate() 就会把每次下降当成“计数器重置”从而给出离谱的结果。反过来,把 counter 标成 gauge,你就失去了 rate() 的正确语义。类型错了,查询必然错。

counter:为什么必须用 rate()

counter 的绝对值几乎没有意义**——“这个进程自启动以来处理了 10 万个请求”有什么用?你要的是“现在每秒多少请求”,这必须靠 rate() 算。**

# ❌ 没意义:一个自启动累计的数,进程重启就归零
http_requests_total

# ✅ 有意义:过去 5 分钟的平均每秒请求数
rate(http_requests_total[5m])

rate() 还会自动处理“计数器重置”:进程重启后计数器归零,rate() 检测到值下降就知道发生了重置,会正确地把它当作“从 0 重新开始”而不是算出一个巨大的负数。这个自动处理是 counter 语义的核心价值——它让“进程会重启”这件事对查询透明。

命名约定:counter 应以 _total 结尾(http_requests_total)。这不只是风格——OpenMetrics 标准把它规范化了,工具链会依赖这个后缀。

gauge:直接看,但小心聚合

gauge 是瞬时值,可上可下:内存占用、队列深度、温度、当前并发数。查询上直接看就行,但聚合时要想清楚语义:

  • sum(node_memory_used_bytes) —— 合理,总内存用量。
  • sum(node_cpu_temperature) —— 荒谬,温度求和没有物理意义。该用 avg 或 max。

gauge 的坑在于“能算不代表该算”——PromQL 不会拦你把温度加起来。想清楚这个量能不能相加,是选聚合函数的唯一标准。

histogram vs summary:最经典的实战决策

这是四种类型里唯一需要认真权衡的地方,也是新手最常踩的坑。两者都用来测分布(比如延迟),但机制根本不同:

histogram:客户端只数桶

一个 histogram 暴露出来其实是三组 series:

# 落在各个桶里的累计计数(le = less than or equal)
http_request_duration_seconds_bucket{le="0.1"}   240
http_request_duration_seconds_bucket{le="0.5"}   980
http_request_duration_seconds_bucket{le="1"}     1000
http_request_duration_seconds_bucket{le="+Inf"}  1002
# 所有观测值之和 + 观测次数
http_request_duration_seconds_sum    312.5
http_request_duration_seconds_count  1002

客户端只做一件事:把观测值扔进对应的桶,给桶计数。分位数是查询时由 histogram_quantile() 从桶里估算出来的。

summary:客户端算好分位数

http_request_duration_seconds{quantile="0.5"}   0.23
http_request_duration_seconds{quantile="0.9"}   0.71
http_request_duration_seconds{quantile="0.99"}  1.84
http_request_duration_seconds_sum    312.5
http_request_duration_seconds_count  1002

客户端在本地维护一个滑动窗口、算出分位数直接暴露。

决定性的区别:summary 的分位数不能跨实例聚合,histogram 的可以。这一条几乎单独决定了选型。

想想你有 10 个 pod,要算整个服务的 P99:

  • summary —— 每个 pod 给你一个“我自己的 P99”。10 个 P99 求平均 = 毫无意义的数。P99 是个分位数,不是可加的量——“平均的 P99”在数学上什么都不是。你无法从中还原出全局 P99,除非只有一个实例。
  • histogram —— 每个 pod 给你桶计数。桶计数是可加的!sum by (le) (rate(..._bucket[5m])) 把 10 个 pod 的桶加起来,得到全局的分布,再 histogram_quantile(0.99, ...) 算出真正的全局 P99。
# ✅ 正确的全局 P99(histogram)
histogram_quantile(0.99, sum by (le) (rate(http_request_duration_seconds_bucket[5m])))

在任何多实例的服务里(也就是几乎所有服务),这一条就宣判了 summary 出局。 这就是为什么业界的默认建议是:优先用 histogram。

那 summary 还有什么用

公平地说,summary 也有它的场合:

维度histogramsummary
跨实例聚合✅ 可以(桶可加)❌ 不行(致命)
精度受桶边界限制(估算)客户端精确算
桶要预先定好✅ 是(选不好就废)不用
客户端开销低(就是几个计数器)高(要维护滑动窗口排序)
服务端开销桶多则 series 多series 少

summary 只适合:单实例、且你需要精确分位数、且能接受不能聚合。 现实里这个交集很小。

histogram 的代价是“桶必须预先定好”,而且选不好就白搭。 如果你的桶是 [0.1, 0.5, 1, 5],而真实延迟全在 0.6~0.9 之间,那么 P99 只能被估算成“在 0.5 和 1 之间的某处”——线性插值出来的数几乎没有参考价值。

实战原则:桶要覆盖你关心的区间,且在 SLO 阈值附近加密。 如果你的 SLO 是“P99 < 300ms”,那 300ms 附近必须有桶(如 [0.1, 0.2, 0.25, 0.3, 0.35, 0.5, 1, ...]),否则你根本判断不了 SLO 有没有破。桶的设计 = 你想问的问题的精度上限。第 2 部分会给常见场景的桶推荐。

Prometheus 3.x 的 native histograms 正是来解这个痛点:桶自动、指数式生成,不用预先定,精度还高得多。这是近年最重要的演进,第 3 部分细讲。

一张选型速查

图表加载中…

小结与下一章

四种类型是语义承诺,决定你该怎么查:counter(只增,命名 _total,必须 rate();rate() 自动处理重启归零)、gauge(瞬时值,聚合前想清楚“这个量能不能相加”)、histogram(分桶:_bucket{le}/_sum/_count,查询时 histogram_quantile() 估算)、summary(客户端算好分位数)。最关键的实战决策是 histogram vs summary,答案几乎总是 histogram——因为summary 的分位数不能跨实例聚合(“平均的 P99”毫无意义),而 histogram 的桶计数可加,能算出真正的全局分位数。histogram 的代价是桶要预先定好且选不好就废——桶必须在 SLO 阈值附近加密;native histograms 正是来解这个痛的。

类型定了,指标怎么吐出去?下一章看暴露格式与 OpenMetrics。👉 暴露格式。

参考资料

  • [1] Prometheus 源码 model/textparse/promparse.go:337(四种类型的解析)、openmetricsparse.go、protobufparse.go:654(_count/_sum/_bucket 后缀)。
  • [2] Prometheus 官方文档:Metric types、Histograms and summaries。
  • [3] 关联数据模型、指标体系方法论。

本页目录