4. PromQL 基础:语法、选择器与匹配模式、常用函数
好,咱们进入 PromQL 的世界。说实话,PromQL 是 Prometheus 的灵魂。你告警规则写得再花哨,数据拿不出来,一切都是白搭。我个人习惯把 PromQL 分成三块来学:语法结构、数据筛选、以及那几个高频函数。咱们一个一个来啃。
4.1 PromQL 语法:其实就两种数据类型
PromQL 的查询结果,说白了就两种形态:瞬时向量 和 范围向量。
- 瞬时向量:某个时间点的一组数据。比如
node_cpu_seconds_total,查的是“现在这一刻”的 CPU 时间。 - 范围向量:一段时间内的数据。比如
node_cpu_seconds_total[5m],查的是过去 5 分钟的所有采样点。
嗯,这里要注意:范围向量不能直接画图,也不能直接用于告警。你得配合 rate、increase 这类函数,把它转成瞬时向量才行。我刚开始学的时候,老是在这上面栽跟头。
核心记忆点:
- 不加
[时间]→ 瞬时向量 - 加了
[时间]→ 范围向量 - 范围向量必须经过函数处理才能用
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
这条规则的意思是:
- 取过去 5 分钟每个 CPU 核的 idle 时间,算出每秒变化率
- 按 instance 做平均,得到每个机器的平均 idle 率
- 用 100 减去 idle 率,得到 CPU 使用率
- 如果使用率超过 80%,触发告警
你想想看,如果不用 rate(),直接用 node_cpu_seconds_total,那算出来的是从开机到现在的累积值,根本没法用。这就是 PromQL 的精髓——把原始数据转换成有意义的指标。
最后提醒一句: PromQL 的查询是有性能开销的。别在 Grafana 面板里写那种 {__name__=~".*"} 的全量查询,会把 Prometheus 拖垮。能用精确匹配就别用正则,能用 rate() 就别用 irate()。稳,才是运维的第一要义。