5月27日 17:44

Prometheus 指标类型怎么选?Counter Gauge Histogram Summary 区别与用法

Prometheus 监控的核心不是采集数据,而是用对指标类型。选错了类型,要么查询结果不准,要么聚合不出你想要的维度。Counter、Gauge、Histogram、Summary 四种类型各有明确的使用场景,搞清楚它们的差异是写出可靠监控规则的第一步。

Counter:只增不减的累计值

Counter 最简单的理解:出租车计价器。数字只会往上走,不会倒退(除非进程重启归零)。

典型场景:HTTP 请求总数、错误发生次数、任务完成数。这些值天然只会累加。

promql
# 计算 QPS——最近 5 分钟每秒平均请求数 rate(http_requests_total[5m]) # 计算增长量——最近 1 小时新增了多少请求 increase(http_requests_total[1h])

Counter 的查询几乎离不开 rate()increase()。直接看 Counter 的原始值意义不大——"总共处理了 100 万个请求"本身不能告诉你系统现在健不健康,但"每秒处理 500 个请求"就能。

定义 Counter 指标的方式:

go
var httpRequestCounter = prometheus.NewCounterVec( prometheus.CounterOpts{ Name: "http_requests_total", Help: "Total number of HTTP requests", }, []string{"method", "path", "status"}, )

注意 NewCounterVecVec 后缀——它支持按 label 拆分维度(按方法、路径、状态码分别统计),这在实战中非常常用。

容易踩的坑:把本该用 Gauge 的值(比如当前在线用户数)定义成 Counter。在线用户数会减,Counter 不能减,强行用 Counter 记录会导致 rate() 计算出负值,图表出现锯齿状异常。

Gauge:可增可减的瞬时值

Gauge 像温度计——当前多少就是多少,可以升高也可以降低。

典型场景:当前内存使用量、CPU 使用率、队列深度、在线连接数。这些值的"当前值"本身就有意义,不需要算变化率。

promql
# 直接看当前值 node_memory_available_bytes # 看过去 1 小时的峰值 max_over_time(node_memory_available_bytes[1h]) # 看过去 30 分钟的平均值 avg_over_time(node_cpu_seconds_total[30m])

Gauge 用 max_over_timemin_over_timeavg_over_time 这类函数。注意:不要对 Gauge 用 rate()——rate() 计算的是增长率,而 Gauge 的值波动本身不表示增长趋势,算出来的结果没有意义。

go
var tempGauge = prometheus.NewGaugeVec( prometheus.GaugeOpts{ Name: "room_temperature_celsius", Help: "Current room temperature", }, []string{"room"}, ) // 设置值 tempGauge.WithLabelValues("server-room").Set(23.5)

Gauge 可以用 Set() 直接设置任意值,也可以用 Inc() / Dec() 增减,这是和 Counter 的本质区别。

Histogram:服务端算分位数的分布

当你需要知道"95% 的请求在多长时间内完成"时,就需要 Histogram。

Histogram 的工作原理:定义一组 bucket(桶的边界),每个观测值落入对应的桶中。比如配置 bucket 为 [0.1, 0.5, 1, 2.5, 5, 10],一个耗时 0.3 秒的请求会同时被计入 le=0.5le=1le=2.5... 所有比它大的桶里。

go
var requestDuration = prometheus.NewHistogramVec( prometheus.HistogramOpts{ Name: "http_request_duration_seconds", Help: "HTTP request duration", Buckets: []float64{0.1, 0.5, 1, 2.5, 5, 10}, }, []string{"method"}, )

Histogram 会自动产生三个指标:

  • http_request_duration_seconds_bucket{le="0.5"} — 落入各桶的累计计数
  • http_request_duration_seconds_sum — 所有观测值之和
  • http_request_duration_seconds_count — 观测总次数

histogram_quantile 在服务端计算分位数:

promql
# P95 延迟 histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m]))

Histogram 的关键优势:可以聚合。多个实例的 bucket 数据可以在 Prometheus 服务端合并计算,适合分布式系统。代价是分位数值是近似值(受 bucket 粒度影响),bucket 配得太粗会导致误差大。

bucket 怎么配:先观察数据的实际分布,再设定桶边界。初始可以用 Prometheus 默认的桶(适合 Web 请求延迟),上线后根据 P99 的实际范围调整。桶的数量建议 8-12 个,太少了精度差,太多了存储膨胀。

Summary:客户端算分位数的精确值

Summary 和 Histogram 解决同样的问题——算分位数,但计算位置不同:Summary 在客户端(你的应用里)直接算好分位数再上报。

go
var rpcDuration = prometheus.NewSummaryVec( prometheus.SummaryOpts{ Name: "rpc_duration_seconds", Help: "RPC call duration", Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}, }, []string{"service"}, )

Objectives 定义了你关心哪些分位数及可接受的误差。0.9: 0.01 表示 P90 的误差在 1% 以内。

Summary 产出的指标:

  • rpc_duration_seconds{quantile="0.9"} — P90 延迟(客户端已算好)
  • rpc_duration_seconds_sum — 总和
  • rpc_duration_seconds_count — 总次数

直接查询即可:

promql
# P99 延迟,无需 histogram_quantile rpc_duration_seconds{quantile="0.99"}

但 Summary 有个致命限制:不能跨实例聚合。你有 10 个 Pod,每个 Pod 自己算了 P99,你没法把这 10 个 P99 合并成全局 P99。数学上,分位数的分位数不是总体的分位数。所以 Summary 只适合单实例场景,或者你只关心每个实例自己的延迟分布。

Histogram 还是 Summary?

这是面试和实战中最高频的问题,核心差异就一点:分位数在哪里算

对比项HistogramSummary
分位数计算位置Prometheus 服务端应用客户端
可聚合可以(跨实例合并计算)不可以
精确度近似值(受 bucket 粒度影响)可控误差(由 Objectives 设定)
客户端开销低(只计数)较高(需要维护分位数流式计算)
适用场景分布式系统、多实例单实例、需要精确分位数

选择逻辑很简单:多实例分布式系统用 Histogram,单实例或需要精确值用 Summary。大多数生产环境选 Histogram,因为可聚合是刚需。

四种类型怎么选

决策路径:先问"这个值是只增不减的吗"——是就用 Counter,不是就问"当前瞬时值本身有意义吗"——有意义用 Gauge,没意义再问"需要算分位数吗"——需要就问"要多实例聚合吗"——要聚合用 Histogram,不要用 Summary。

举几个实战中的对应关系:

  • API 请求总数 → Counter(只增不减)
  • 当前活跃连接数 → Gauge(可增可减,当前值有意义)
  • 请求延迟分布 → Histogram(需要分位数 + 聚合)
  • 单进程内部方法耗时 → Summary(单实例精确分位数)

Prometheus 服务端本身其实不区分这四种类型——所有时间序列在服务端都是一样的存储格式。类型信息只存在于客户端库中,目的是约束你用正确的 API 记录数据、用正确的函数查询数据。选对类型,查询才有意义;选错了,数据采集了也用不上。

标签:Prometheus