4、PromQL 入门:即时向量与范围向量、选择器、聚合操作符、常用函数(rate, irate, increase)

好,咱们进入 PromQL 的核心地带。说实话,很多刚接触 Prometheus 的朋友,第一反应是:「这查询语言怎么看着怪怪的?」

嗯,我当年也一样。但用顺手之后你会发现,PromQL 的设计其实非常优雅。它专门为时序数据而生,没有多余的废话。

今天这一讲,我带你把 PromQL 的骨架搭起来。掌握了这些,你就能看懂 80% 的告警规则和监控面板了。

4.1 即时向量与范围向量:理解 PromQL 的数据模型

这是 PromQL 最基础、也最容易混淆的概念。我刚开始学的时候,也在这上面栽过跟头。

4.1.1 即时向量(Instant Vector)

说白了,就是「当前这一刻」的数据快照。

比如你查 node_cpu_seconds_total,返回的是每个 CPU 核在当前时间点的最新值。每个时间序列只有一条数据。

关键特征:

  • 每个时间序列只返回一个样本点
  • 查询时不需要指定时间范围
  • 常用于仪表盘上的「当前值」展示

举个例子:

node_memory_MemAvailable_bytes

这条查询返回的就是「此时此刻」每台机器的可用内存。嗯,就这么简单。

4.1.2 范围向量(Range Vector)

范围向量就不一样了。它返回的是「过去一段时间」的数据。

你想想看,如果你要看 CPU 使用率的变化趋势,光看当前值肯定不够。你需要过去 5 分钟的数据,对吧?

node_cpu_seconds_total[5m]

这条查询返回的是每个 CPU 核在过去 5 分钟内的所有采样点。注意,这里返回的是多个值,不是一个。

我曾经踩过的坑: 范围向量不能直接用于绘图!你必须配合函数(比如 rate())才能得到可用的结果。我第一次用 Grafana 时,直接把 [5m] 丢进去,结果面板一片空白。嗯,后来才明白,范围向量只是「原材料」,需要加工才能用。

类型 返回数据量 典型用途 示例
即时向量 每个序列 1 个值 当前状态、告警阈值 node_load1
范围向量 每个序列 N 个值 趋势分析、速率计算 node_load1[5m]

4.2 选择器:怎么精确找到你要的数据

PromQL 的选择器,说白了就是「过滤条件」。你总不想查 CPU 时把内存数据也带出来吧?

4.2.1 精确匹配(=)

最常用的方式。我习惯用它来定位特定实例:

node_cpu_seconds_total{instance="192.168.1.1:9100"}

4.2.2 正则匹配(=~)

当你有一批机器需要一起查时,正则就派上用场了:

node_cpu_seconds_total{instance=~"192.168.1.*:9100"}

这个查询会匹配所有 192.168.1.x 段的机器。我在管理上百台服务器时,这个功能帮我省了不少事。

4.2.3 反向匹配(!=, !~)

排除某些数据。比如你想看所有非空闲的 CPU 状态:

node_cpu_seconds_total{mode!="idle"}

我的个人习惯: 写选择器时,尽量把过滤条件写精确。不要偷懒写 {job=~".*"},这会拉回所有数据,性能会很差。我曾经见过一个同事,一条查询跑了 30 秒还没出结果,就是因为选择器太宽泛了。

4.3 聚合操作符:把多条数据揉在一起

有时候你不需要看每台机器的细节,你只需要一个「总和」或「平均值」。聚合操作符就是干这个的。

4.3.1 常用聚合操作符

操作符 作用 我常用的场景
sum 求和 计算所有机器的总 CPU 使用量
avg 平均值 查看集群的平均负载
max / min 最大 / 最小值 找出最忙和最闲的机器
count 计数 统计有多少台机器在运行
topk / bottomk 前 K 个 / 后 K 个 找出 CPU 最高的前 5 台机器

4.3.2 配合 by 子句使用

聚合时,你可以指定按什么维度分组。比如:

sum by (instance) (node_cpu_seconds_total{mode="idle"})

这条查询会按 instance 分组,计算每台机器的总空闲 CPU 时间。如果不加 by,它会把所有机器的数据揉成一个值——这通常不是我们想要的。

避坑指南: 我曾经在写告警规则时,忘了加 by 子句,结果聚合出来的值完全不对。一条本该触发告警的规则,硬是没响。嗯,从那以后,我每次写聚合都会先问自己:「我要按什么维度分组?」

4.4 常用函数:rate, irate, increase

这三个函数,是 PromQL 里最常用的「三剑客」。我敢说,你写的每三条查询里,至少有一条会用到它们。

4.4.1 rate():平滑的速率计算

rate() 计算的是「每秒平均增长率」。它会在指定时间范围内取平均值,所以曲线比较平滑。

rate(node_cpu_seconds_total{mode="idle"}[5m])

这条查询返回的是:过去 5 分钟内,CPU 空闲时间的每秒平均增长率。

为什么用 rate() 而不是直接看原始值?因为 Counter 类型的指标(比如 node_cpu_seconds_total)是单调递增的,直接看数值没有意义。你需要知道的是「增长的速度」。

我的建议: 在大多数场景下,优先用 rate()。它抗抖动能力强,适合做告警和长期趋势分析。

4.4.2 irate():灵敏的瞬时速率

irate() 计算的是「基于最后两个采样点的瞬时增长率」。它不取平均,所以对突发变化非常敏感。

irate(node_cpu_seconds_total{mode="idle"}[5m])

你看,语法和 rate() 几乎一样,但行为完全不同。

irate() 的曲线锯齿感很强,适合用来观察短时间内的突发流量。但我不建议用它来做告警——太灵敏了,容易误报。

函数 计算方式 曲线特点 推荐场景
rate() 时间范围内平均 平滑 告警、长期趋势
irate() 最后两个点 锯齿状、灵敏 实时监控、突发检测

4.4.3 increase():绝对增量

increase() 返回的是「指定时间范围内的总增量」。它和 rate() 本质上是同一个东西,只是单位不同。

increase(node_network_receive_bytes_total[1h])

这条查询告诉你:过去 1 小时内,每块网卡总共收到了多少字节。

我个人习惯用 increase() 来做容量规划。比如看过去 24 小时的总流量,用 increase() 就很直观。

注意: increase() 的结果受时间范围影响很大。同样是查网络流量,[5m] 和 [1h] 的结果会差很多倍。嗯,这听起来是废话,但我真见过有人把这两个搞混,然后告警阈值设得乱七八糟。

4.5 实战组合:一个完整的查询示例

好了,理论讲完了。咱们来写一条真实的查询,把今天学的串起来。

假设你想知道:当前集群中,CPU 使用率最高的前 5 台机器是哪些?

topk(5, 
  sum by (instance) (
    rate(node_cpu_seconds_total{mode!="idle"}[5m])
  )
)

这条查询干了三件事:

  1. rate(...[5m]):计算每台机器每个 CPU 核的非空闲时间增长率
  2. sum by (instance):按机器聚合,得到每台机器的总 CPU 使用率
  3. topk(5, ...):取前 5 名

你看,把今天学的知识点组合起来,就能写出很实用的查询了。

最后说一句: PromQL 的学习,没有捷径。我建议你打开 Prometheus 的 Web UI,在「Graph」页面里多试。写错了没关系,PromQL 的报错信息还算友好。多练几次,你就能找到感觉了。