4. PromQL 基础:语法、选择器与匹配模式、常用函数

好,咱们进入 PromQL 的世界。说实话,PromQL 是 Prometheus 的灵魂。你告警规则写得再花哨,数据拿不出来,一切都是白搭。我个人习惯把 PromQL 分成三块来学:语法结构、数据筛选、以及那几个高频函数。咱们一个一个来啃。

4.1 PromQL 语法:其实就两种数据类型

PromQL 的查询结果,说白了就两种形态:瞬时向量范围向量

  • 瞬时向量:某个时间点的一组数据。比如 node_cpu_seconds_total,查的是“现在这一刻”的 CPU 时间。
  • 范围向量:一段时间内的数据。比如 node_cpu_seconds_total[5m],查的是过去 5 分钟的所有采样点。

嗯,这里要注意:范围向量不能直接画图,也不能直接用于告警。你得配合 rateincrease 这类函数,把它转成瞬时向量才行。我刚开始学的时候,老是在这上面栽跟头。

核心记忆点:

  • 不加 [时间] → 瞬时向量
  • 加了 [时间] → 范围向量
  • 范围向量必须经过函数处理才能用

4.2 选择器与匹配模式:怎么精准捞数据

你想想看,生产环境里 metric 名字就那么几个,但 label 千奇百怪。怎么从一堆数据里捞出你想要的那条?靠选择器。

4.2.1 标签匹配器

PromQL 支持四种匹配模式,我列个表给你看,一目了然:

匹配器 含义 示例
= 精确相等 job="api-server"
!= 不等于 job!="api-server"
=~ 正则匹配 job=~"api.*"
!~ 正则排除 job!~"api.*"

我在项目中遇到过一个问题:有个同事写告警规则,用了 job=~".*" 想匹配所有 job,结果因为正则引擎的贪婪匹配,把一些不该匹配的 label 也捞进来了。后来我建议他,能用 = 就别用正则,性能差很多。

我的小技巧: 写正则匹配时,尽量用 ^$ 锚定开头结尾。比如 job=~"^api.*$",避免意外匹配。

4.2.2 向量匹配模式

当你需要把两个查询结果做运算时(比如算 CPU 使用率),就涉及到匹配模式。PromQL 有三种:

  • 一对一匹配:两边 label 完全一致,直接运算。
  • 多对一 / 一对多匹配:一边 label 多,一边 label 少。用 on()ignoring() 指定匹配哪些 label。
  • 多对多匹配:两边 label 都不唯一,一般很少用。

举个例子,计算 CPU 使用率:

100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)

这里 avg by (instance) 就是一对多匹配——把多个 CPU 核的 idle 数据,按 instance 聚合成一个值。

我曾经踩过的坑:on() 时,忘了把 job 也加进去。结果两个不同 job 的 instance 同名,数据串了。排查了半天才发现是 label 匹配不全。所以,写 on() 时,把能唯一标识的 label 都列上。

4.3 常用函数:rate、increase、histogram_quantile

这三个函数,我敢说,你写告警规则时 80% 的场景都会用到。咱们一个一个说。

4.3.1 rate():算每秒增长率

rate() 是 PromQL 里最常用的函数。它计算的是 每秒平均增长率。注意,是“平均”。

rate(http_requests_total[5m])

这条语句的意思是:取过去 5 分钟的 http_requests_total 数据,算出每秒平均涨了多少。为什么用 5 分钟?因为 Prometheus 默认抓取间隔是 15 秒,5 分钟能拿到 20 个样本点,算出来的值比较平滑。

我个人习惯,在告警规则里用 rate() 而不是 irate()。虽然 irate() 反应更快,但容易抖动。告警嘛,稳一点好。

4.3.2 increase():算一段时间内的增量

increase()rate() 很像,但它返回的是 绝对增量,而不是每秒速率。

increase(http_requests_total[1h])

这条语句返回的是:过去 1 小时内,总共增加了多少请求。嗯,这里要注意:increase() 在 Counter 重置时也能正确处理,但如果你抓取间隔不固定,算出来的值可能会有误差。

什么时候用 rate,什么时候用 increase?

  • 想看趋势、做告警 → 用 rate()
  • 想看总量、做容量规划 → 用 increase()

4.3.3 histogram_quantile():算分位数

这个函数稍微复杂一点。它用于 Histogram 类型的 metric,计算某个分位数的值。比如 P99 延迟。

histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m]))

这条语句的意思是:取过去 5 分钟的请求延迟数据,算出 P99 延迟是多少秒。为什么外面要套一层 rate()?因为 Histogram 的 bucket 是累积的,不套 rate() 算出来的是历史累积值,不是当前时段的值。

我在项目中遇到过一个问题:有个同事直接用 histogram_quantile(0.99, http_request_duration_seconds_bucket),结果算出来的 P99 一直不变。我一看,原来是没加 rate(),导致算的是从服务启动到现在的累积分位数。加上 rate() 后,数据就正常了。

我的建议:histogram_quantile() 时,一定要配合 rate()increase() 使用。另外,bucket 的边界设置要合理。如果 bucket 太粗,算出来的分位数精度会很低。

4.4 实战小例子:写一个 CPU 使用率告警

咱们把上面学的串起来,写一个实际的告警规则:

100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 80

这条规则的意思是:

  1. 取过去 5 分钟每个 CPU 核的 idle 时间,算出每秒变化率
  2. 按 instance 做平均,得到每个机器的平均 idle 率
  3. 用 100 减去 idle 率,得到 CPU 使用率
  4. 如果使用率超过 80%,触发告警

你想想看,如果不用 rate(),直接用 node_cpu_seconds_total,那算出来的是从开机到现在的累积值,根本没法用。这就是 PromQL 的精髓——把原始数据转换成有意义的指标。

最后提醒一句: PromQL 的查询是有性能开销的。别在 Grafana 面板里写那种 {__name__=~".*"} 的全量查询,会把 Prometheus 拖垮。能用精确匹配就别用正则,能用 rate() 就别用 irate()。稳,才是运维的第一要义。