bigywater.tech
微服务 · 68

指标的类型:counter、gauge、histogram

counter、gauge、histogram 各自该用在什么地方?gauge 会漏掉尖峰吗?进程重启 counter 归零怎么办?为什么不能把各实例的 p99 求平均?对 gauge 用 rate 会怎样?这篇用 Prometheus 3.15.0 逐项量出来。

4 / 10
抓取间隔 5 s、10 个持续 2 s 的尖峰,gauge 抓到的样本里碰上尖峰的个数(counter 的 increase 是 19.9 s,实际 20.3 s)
282.2 vs 508.7
两个实例 p99 的平均值与合并后的真实 p99(ms)
14.41
对 gauge 用 rate() 得到的每秒速率,这个数没有意义(deriv 是 −1.25)

gauge 的采样

实测 · 本机执行输出
  指标                         | 抓到的样本数 | 结果                | 备注                 
  ---------------------------+--------+-------------------+--------------------
  gauge inflight             | 20 个样本 | 4 个样本是 100        | max_over_time = 100
  counter busy_seconds_total | 20 个样本 | increase = 19.9 s | 应用实际在尖峰里的时间 20.3 s 
  共 10 个尖峰,每个 2 s;抓取间隔 5 s,一个 2 s 的尖峰被某次抓取碰上的概率约 2/5。gauge 只记录抓取那一刻的值,没碰上的尖峰就看不到;counter 记录累计量,两次抓取之间发生的变化会在下一次抓取时全部体现,increase 能还原总量(counter 在抓取边界处有外推的误差)

gauge 只记录抓取时刻的值,两次抓取之间的尖峰会漏掉;counter 的累计量能还原总量。

counter 重置

实测 · 本机执行输出
  计算方式                           | 结果  
  -------------------------------+-----
  最后一个样本 − 第一个样本                 | 1600
  按重置规则手算:(最后 − 第一个) + 重置前的最后一个值 | 3600
  increase(requests_total[2m])   | 3685
  rate(requests_total[2m]) × 120 | 3685
  应用实际处理的请求数                     | 4000
  抓到 39 个样本,第一个样本 400,最后一个样本 2000;样本序列里检测到的“值变小”的位置:[(2000.0, 100.0)]。increase() 和 rate() 把“值变小”当作 counter 重置,重置之前的值也算进去,所以比“最后 − 第一个”大得多;它们和手算值的差别来自窗口边界的外推;手算值也比应用实际处理的少,因为第一个样本之前的增量不在样本里

increase() / rate() 把值变小当作重置,把重置之前的值算进去;直接相减会少算。

分位数聚合

实测 · 本机执行输出
  计算方式                                                     | p99(ms)
  ---------------------------------------------------------+--------
  所有 20000 个请求的真实 p99                                      | 508.7  
  A 自己的 p99                                                | 510.1  
  B 自己的 p99                                                | 54.2   
  两个 p99 取平均(PromQL avg(req_p99_ms))                       | 282.2  
  两个 p99 按请求量加权平均(计算)                                      | 464.5  
  histogram_quantile(0.99, sum by (le) (increase(bucket))) | 732.6  
  预先算好的分位数不能求平均:不管是简单平均还是按请求量加权,都不等于合并后的真实 p99;histogram 的桶是计数,可以先相加再估计分位数,但估计值是在桶内线性插值得到的:这里慢请求(500~520 ms)都落在 (500, 1000] 这个宽桶里,插值的结果和真实值差得比较多(见下一篇)

预先算好的分位数不能平均;histogram 的桶可以相加,但估计值取决于桶的划分。

类型是约定

实测 · 本机执行输出
  PromQL                                  | 结果    
  ----------------------------------------+-------
  rate(hits_total[30s])(每秒 +10 的 counter) | 9.31  
  rate(level[30s])(对 gauge 用 rate)        | 14.41 
  deriv(level[30s])                       | -1.25 
  delta(level[30s])                       | -22.76
  level 当前值                               | 12    
  rate() 是为 counter 设计的:它把“值变小”当作重置,gauge 下降的那一段会被当成重置、从 0 重新算;对 gauge 应该用 deriv()(最小二乘斜率)或 delta()(首尾差)
ERRORS {}

Prometheus 不强制类型:rate() 用在 gauge 上不会报错,但结果没有意义。

分步演示

指标的类型
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Python 3.14.8 / Go / nginx / Envoy / etcd / Consul / Nacos / OpenTelemetry / Prometheus(按各篇注明用到哪些及版本);所有服务都是本机自己起的进程,端口 9150 以上,只监听 127.0.0.1,不连任何外部服务,不碰本机已有的实例;故障用 kill -9 进程、让连接断开或在代码里注入延迟与错误来制造;计时用 time.perf_counter,数字每次运行会有小幅抖动。Prometheus 3.15.0,prometheus_client 0.26.0;各节的抓取间隔不同(第一节 5 s,其余 1 s);尖峰时刻与延迟样本都用固定种子生成,第一节每次运行抓到的样本数和碰上尖峰的个数略有不同(开发中的另一次运行是 21 个样本、5 个碰上);histogram_quantile 的结果每次运行略有波动(731.1~732.6);每节只跑了一次
    • 没有验证:Summary 类型(prometheus_client 的 Python 版 Summary 没有分位数,所以没有测);OpenMetrics 的 exemplar;native histogram(Prometheus 3.x 的新直方图,没有开启);Prometheus 对 counter 重置的启发式在高频重置下的表现;StatsD / OTel 指标的 delta 与 cumulative 差异(见后面的篇目)
    京ICP备2026067502号-1京公网安备11010502063487号