四种指标类型 · 以及怎么选
counter、gauge、histogram、summary —— 四种类型对应四种度量语义。这一章讲清各自适用什么、命名约定,并重点拆解那个最经典的实战决策:histogram 还是 summary(答案几乎总是 histogram,原因是可聚合性)。
四种指标类型 · 以及怎么选
上一章说 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 也有它的场合:
| 维度 | histogram | summary |
|---|---|---|
| 跨实例聚合 | ✅ 可以(桶可加) | ❌ 不行(致命) |
| 精度 | 受桶边界限制(估算) | 客户端精确算 |
| 桶要预先定好 | ✅ 是(选不好就废) | 不用 |
| 客户端开销 | 低(就是几个计数器) | 高(要维护滑动窗口排序) |
| 服务端开销 | 桶多则 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。👉 暴露格式。