
在 2024 年的当下,Prometheus 生态基本已成为监控领域事实上的标准,学习 Prometheus 是每个运维人员的必修课,也是每个关注服务稳定性的研发人员的必修课。PromQL 是 Prometheus 的查询语言,全称是 Prometheus Query Language,想要学习 Prometheus,PromQL 是必学知识。本文是 PromQL 系列教程的第二讲,讲解 Prometheus 数据类型。本系列其他文章:
核心摘要
- Prometheus 常见指标类型包括 Gauge、Counter、Histogram 和 Summary。
- Gauge 表示当前状态,可上升也可下降;Counter 表示单调递增总量,通常用来计算增量或速率。
- Histogram 和 Summary 都能描述请求量、耗时和分位值,但 Histogram 更适合在服务端聚合计算全局分位。
- Prometheus 服务端时序库本身不存储指标类型,类型主要在客户端埋点和 PromQL 使用方式中体现。
rate函数应主要用于 Counter 类型;如果拿 Gauge 去算 rate,结果通常没有实际意义。
Prometheus 数据类型
在学习 PromQL 之前,我们先来了解一下 Prometheus 的数据类型。Prometheus 中有四种基本数据类型:Gauge、Counter、Histogram 和 Summary。区分不同类型,是为了对现实世界建模,更好地反映不同指标的变化特性。
Gauge
Gauge 类型的值表示当前状态,可大可小、可负可正。比如某个虚机实例挂了,用 0 表示;如果实例存活,用 1 表示。再比如内存使用率,这个时刻采集是 33.7%,下个周期可能变成 25.8%。机器最近 5 分钟的 load、正在运行的进程数量,也都适合用 Gauge 表示。这种类型的值,我们非常关注当前值。
Counter
Counter 类型是单调递增的值,比如机器上某块网卡收到的数据包的总量,是从操作系统启动之后,就持续递增的,对于这种类型的值,我们通常关注的不是当前值是多少,而是关注增量和变化率。我们在机器上执行 ifconfig 命令:
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 10.206.0.16 netmask 255.255.240.0 broadcast 10.206.15.255
inet6 fe80::5054:ff:fed2:a180 prefixlen 64 scopeid 0x20<link>
ether 52:54:00:d2:a1:80 txqueuelen 1000 (Ethernet)
RX packets 457952401 bytes 125894899868 (117.2 GiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 518040495 bytes 276312546157 (257.3 GiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
RX packets 后面的值是 OS 启动以来收到的总的包量,TX packets 后面的值是 OS 启动以来发出去的总的包量,都是很大的值,我们通常不太关注这个值当前是多少,更关注的是最近 1 分钟收到/发出多少包,或者每秒收到/发出多少包。
而对于监控数据采集器而言,一般是周期性运行的,比如每 10 秒采集一次,每次采集网卡收到/发出的包这个数据的时候,都只能采集到当前的值,就像执行 ifconfig 命令,每 10 秒执行一次,每次都看到一个巨大的当前值,而且一次比一次大。如果采集器不做计算,把这个值原封不动上报给监控服务端,那计算增量、计算速率这个需求,就要放到服务端来实现了,所以服务端必须要能对这种类型的数据建模抽象,也就是所谓的 Counter 类型。
计算增量、速率这样的动作也可以放到客户端 agent 采集器里来做,但是灵活性就不好了,因为 agent 一旦得到增量、速率这样的计算结果,就会把原始数据丢弃,但是 agent 计算增量、速率一定要基于一个时间窗口大小,通常这个窗口大小要固化在 agent 配置中。如果想要按照不同的窗口大小来计算,那就要放到服务端更合适了,服务端保留原始数据,同时支持用户按照不同的窗口大小来动态计算增量、速率。
Histogram
Histogram 类型通常用于描述请求延迟、请求大小的分布情况。比如某个接口,最近一分钟收到 1000 个请求,多少请求是 10 毫秒内返回,多少请求是 100 毫秒内返回,多少请求是 1000 毫秒内返回,就是典型的一个需求场景。有了这个数据之后,就可以很方便的计算出 99 分位的延迟、95 分位的延迟这样的数据,相比平均延迟,分位延迟可以更好的描述一个服务的延迟情况。
Histogram 数据其实是由多个 Counter 指标组成,我查询一个例子给你看看:

n9e_pushgw_forward_duration_seconds 作为前缀的指标有 6 个,分别是:
- 以
_count结尾的指标,表示总的请求量,上例中这个值是 32202,表示 n9e-pushgw 的 forward 动作共计执行了 32202 次 - 以
_sum结尾的指标,表示总的耗时,上例中是 11.055579,表示 n9e-pushgw 的 forward 动作总耗时是 11.055579 秒 - 以
_bucket结尾的指标,表示不同延迟区间的请求数量,上例中有 4 个 bucket,分别是 0.1、1、10、+Inf,表示 0.1 秒内的请求数量、1 秒内的请求数量、10 秒内的请求数量、+Inf 无穷大秒内的请求总数量,+Inf 这个 bucket 的值一定和_count是一样的
上例中各个 bucket 的值都是 32202,是因为 bucket 的划分不合理,划分的过大了,应该增加 less than 0.01 秒这样的区间才有意义。仅就上例而言,传递的信息是:所有的 forward 请求都很快,全部是在 0.1 秒内返回的。所以 le=0.1 的 bucket 的值是 32202,而因为 le=1 的 bucket 是 le=0.1 的超集,所以既然 le=0.1 的 bucket 的值是 32202,那么 le=1 的 bucket 的值也是 32202。le=10 的 bucket 也是一样,因为 le=10 是 le=1 的超集,所以 le=10 的 bucket 的值也是 32202。而 le=+Inf 的 bucket 的值也是 32202,因为 le=+Inf 是全集。
后文会介绍 histogram_quantile 函数,届时会对 Histogram 类型的数据有更详细的介绍。
Summary
Summary 和 Histogram 类似,主要用于描述请求总量、延迟总量、不同的分位数据等。Histogram 也可以计算分位值,Summary 也可以计算分位值,二者有何区别?最核心的点是:Histogram 可以在服务端计算某个服务的所有数据的分位值,Summary 只能在客户端计算某个特定实例颗粒度的分位值。
14 年左右的时候,当时在小米写 Open-Falcon,业务研发就搞了一个 SDK,专门计算各个接口的 Performance 数据,SDK 会内嵌在各个程序里,记录最近一段时间的请求数据,计算 50 分位、99 分位的延迟,回头来看,就相当于 Prometheus 的 Summary 数据。既然是在 SDK 里做的,那这个 Summary 的数据范围,就是实例颗粒度的,某个服务假设部署了 3 个实例,通过 Summary 数据是没法拿到整个服务全局的 99 分位延迟的,只能分别拿到各个实例各自的数据。
类型小结
虽然 Prometheus 有四种基本数据类型,但是在实际使用中,用的最多的是 Gauge 和 Counter 两种类型,作为初学者,即便现在你不理解 Histogram 和 Summary,也不要紧,后面见多了,听多了就懂了。
| 类型 | 核心语义 | 典型场景 | PromQL 注意点 |
|---|---|---|---|
| Gauge | 当前状态,可升可降 | CPU 使用率、内存使用率、load、进程数 | 适合看当前值和趋势 |
| Counter | 单调递增总量 | 请求总数、网络包总数、错误总数 | 常配合 rate 计算速率 |
| Histogram | 分桶统计分布 | 请求延迟、请求大小 | 可在服务端聚合计算分位 |
| Summary | 客户端计算分位 | SDK 内部性能统计 | 通常是实例粒度,难做全局聚合 |
关于类型,你可能不知道的是
实际上,Prometheus 服务端时序库在存储指标的时候,是不区分类型的,比如我作为 client,可以通过 remote write 协议把监控数据推给 Prometheus 服务端,remote write 的数据结构如下:
func Send(WriteRequest)
message WriteRequest {
repeated TimeSeries timeseries = 1;
// Cortex uses this field to determine the source of the write request.
// We reserve it to avoid any compatibility issues.
reserved 2;
// Prometheus uses this field to send metadata, but this is
// omitted from v1 of the spec as it is experimental.
reserved 3;
}
message TimeSeries {
repeated Label labels = 1;
repeated Sample samples = 2;
}
message Label {
string name = 1;
string value = 2;
}
message Sample {
double value = 1;
int64 timestamp = 2;
}
这个 protobuf 数据结构中根本就没有数据类型相关的信息,惊不惊喜意不意外?那既然 Prometheus 服务端时序库不区分数据类型,那数据类型的区分是不是就没有价值了呢?也不是,数据类型会用在两个地方:
- 客户端埋点的时候,提升便利性。比如客户端指定某个指标是 Histogram 类型的话,虽然在调用 SDK 方法的时候,仅仅调用了一个 Observe 方法,实际 SDK 会在底层自动计算多个指标出来。如果指标类型是 Gauge,SDK 就不会额外计算那些指标了。
- 使用 PromQL 查询的时候,其实隐式的告诉查询引擎数据类型。比如在 PromQL 语句中,你用 rate 函数计算某个指标的速率,Prometheus 就会自动识别这是一个 Counter 类型的指标,然后计算速率,自动处理数据 reset 的情况。那如果你用 rate 函数计算一个 Gauge 类型的指标会如何?Prometheus 的 TSDB 中没有数据类型的概念,所以看到 rate 函数,还是会把你传入的指标当做 Counter 类型来对待,发现数据不是单调递增的,就会认为数据发生了重置,导致最终的计算结果是没有意义的。所以,你要保证 rate 函数计算的指标是 Counter 类型的。
演示 Gauge 和 Counter 类型
了解了 Prometheus 的数据类型,下面我们来查询一下 Gauge 和 Counter 类型的数据,看看它们的特点。这俩类型最重要,也是基础。我们之前的例子,使用的 cpu_usage_active 指标,表示 CPU 使用率,这个指标就是 Gauge 类型:

这个指标的特点是上下波动,可大可小。我们尤其关注其当前值,当然,也关注其历史变化趋势。下面我们再来看一个 Counter 类型的指标,比如 net_bits_recv,表示网卡收到的 bit 总量:

Counter 类型的数据,就是一直单调递增的,除非 OS 重启。一般 Counter 类型的数据,我们不关注当前这个巨大的值,没啥意义,而是关注变化率,即每秒收到多少 bit,这个才是我们关注的。使用 Counter 原始数据计算出变化率,就是我们常说的 rate 函数。

这就有波动,有价值了,从图上可以清晰看到,12:00 左右流量有个突增,其他时间流量都比较小。rate 函数接收 range-vector 类型的参数,什么叫 range-vector?可以参考上一篇PromQL教程(一)初识 PromQL。
结论
本文讲解了 Prometheus 的四种指标类型:Gauge、Counter、Histogram 和 Summary。初学 PromQL 时,最先要掌握 Gauge 和 Counter:Gauge 看当前状态,Counter 看增量和速率。Histogram 和 Summary 主要用于请求延迟、请求大小等分布统计,后续学习 histogram_quantile 时会进一步展开。
FAQ
Q1:Prometheus 服务端会存储指标类型吗?
A:不会。remote write 的数据结构里没有类型字段,服务端时序库本身不区分 Gauge、Counter、Histogram 或 Summary。
Q2:既然服务端不存类型,类型还有什么价值?
A:类型在客户端埋点和 PromQL 使用中有价值。客户端 SDK 会按类型生成不同指标;查询时也需要按类型选择合适函数。
Q3:为什么 Counter 要用 rate?
A:Counter 是单调递增总量,当前值通常意义不大。rate 可以把一段时间内的总量变化转换成每秒速率。
Q4:Gauge 可以用 rate 吗?
A:不建议。Gauge 可升可降,用 rate 会被当成 Counter 处理,遇到下降可能被理解为重置,结果通常没有业务意义。
Q5:Histogram 和 Summary 最大区别是什么?
A:原文强调的核心区别是:Histogram 可以在服务端计算某个服务所有数据的分位值,Summary 通常只能在客户端计算特定实例粒度的分位值。
