4. PromQL 查询语言:即时查询、范围查询、聚合操作、Rate/Irate 函数

PromQL 是 Prometheus 的查询语言。说白了,它就是你和监控数据对话的方式。我见过不少运维同事,部署了 Prometheus 却只会看几个预制面板,遇到问题就抓瞎。其实 PromQL 没那么玄乎,掌握几个核心概念,你就能把监控数据玩出花来。

4.1 即时查询与范围查询

这是 PromQL 最基础的两个概念。我刚开始学的时候也混淆过,后来用一句话就记住了:即时查询看当前,范围查询看趋势。

4.1.1 即时查询(Instant Query)

即时查询返回的是某一时刻的样本值。你想想看,就像给监控数据拍了一张快照。

# 查询当前时刻的 CPU 使用率
node_cpu_seconds_total{mode="idle"}

# 查询当前时刻的内存使用量
node_memory_MemAvailable_bytes

返回结果是一个向量,每个时间序列只有一个值。我在项目中遇到过一个问题:用即时查询做告警,结果因为数据采集间隔和告警评估周期没对齐,导致告警频繁抖动。后来我加了个小技巧——用 offset 偏移一下时间,问题就解决了。

小技巧:即时查询配合 offset 可以查看历史某个时刻的数据。比如 node_cpu_seconds_total offset 5m 就是看 5 分钟前的数据。

4.1.2 范围查询(Range Query)

范围查询返回的是一段时间内的样本值。它返回的是一个矩阵,每个时间序列包含多个值。

# 查询过去 5 分钟的 CPU 使用率
node_cpu_seconds_total{mode="idle"}[5m]

# 查询过去 1 小时的内存使用量
node_memory_MemAvailable_bytes[1h]

范围查询的语法就是在指标名后面加 [时间范围]。时间范围支持 s(秒)、m(分)、h(时)、d(天)。

查询类型 返回类型 典型场景
即时查询 瞬时向量 告警判断、当前状态查看
范围查询 区间向量 趋势分析、聚合计算、绘图
注意:范围查询返回的数据量可能很大。如果时间范围设置过长,或者指标基数很高,查询可能会超时。我曾经在生产环境遇到过因为范围查询导致 Prometheus OOM 的情况,后来加了查询超时限制才解决。

4.2 聚合操作

聚合操作就是把多个时间序列合并成一个。为什么要做聚合?因为监控数据太细了。比如你有 100 台机器的 CPU 使用率,你不可能每台都看一遍,你需要一个整体视图。

4.2.1 常用聚合函数

PromQL 提供了丰富的聚合函数,我挑几个最常用的说说:

  • sum:求和。比如所有机器的 CPU 总使用量。
  • avg:平均值。比如集群的平均 CPU 使用率。
  • max / min:最大值 / 最小值。比如找出 CPU 使用率最高的机器。
  • count:计数。比如有多少台机器 CPU 使用率超过 80%。
  • quantile:分位数。比如 P99 响应时间。
# 所有机器的平均 CPU 使用率
avg(node_cpu_seconds_total{mode="idle"})

# 每个机房的 CPU 总使用量
sum(node_cpu_seconds_total{mode="idle"}) by (机房)

# 找出 CPU 使用率最高的机器
max(node_cpu_seconds_total{mode="idle"}) by (instance)

4.2.2 分组聚合

分组聚合是聚合操作的精髓。它允许你按照某个标签维度进行聚合。

# 按 instance 分组,计算每个实例的 CPU 使用率
avg(node_cpu_seconds_total{mode="idle"}) by (instance)

# 按 job 和 instance 分组
avg(node_cpu_seconds_total{mode="idle"}) by (job, instance)

我个人习惯在分组聚合时,只保留必要的标签。标签越多,返回的时间序列就越多,查询效率就越低。我曾经接手过一个面板,里面有个查询用了 by (instance, job, 机房, 集群, 环境),结果每次加载都要等十几秒。后来我砍掉了不必要的标签,查询时间降到了 1 秒以内。

核心原则:聚合时只保留你真正需要的标签。多余的标签不仅浪费资源,还会让图表变得难以阅读。

4.3 Rate / Irate 函数

这两个函数是处理计数器类型指标的利器。计数器(Counter)的特点是只增不减,比如请求总数、CPU 总时间。直接看计数器本身没有意义,你需要看它的变化率。

4.3.1 Rate 函数

rate() 计算的是一段时间内的平均每秒增长率。它会在指定时间范围内计算所有样本的变化率,然后取平均值。

# 过去 5 分钟的平均每秒请求数
rate(http_requests_total[5m])

# 过去 1 小时的平均每秒 CPU 使用率
rate(node_cpu_seconds_total{mode="idle"}[1h])

rate() 适合用于平滑的趋势分析。因为它取了平均值,所以曲线会比较平滑,不会因为个别数据点抖动而剧烈变化。

4.3.2 Irate 函数

irate() 计算的是区间内最后两个样本的瞬时增长率。它只取时间范围内的最后两个数据点,计算它们之间的变化率。

# 过去 5 分钟的瞬时请求数
irate(http_requests_total[5m])

# 过去 1 小时的瞬时 CPU 使用率
irate(node_cpu_seconds_total{mode="idle"}[1h])

irate() 适合用于检测突发的尖峰。因为它只关注最后两个点,所以对变化非常敏感。但缺点也很明显——曲线会非常抖,容易产生误告警。

函数 计算方式 曲线特点 适用场景
rate() 区间内所有样本的平均增长率 平滑 趋势分析、长期告警
irate() 区间内最后两个样本的增长率 抖动 突发检测、短期告警
我的建议:告警规则里优先用 rate(),因为它更稳定。面板展示可以用 irate(),因为它能更快反映变化。我曾经在告警规则里用了 irate(),结果因为网络抖动导致频繁误告警,后来改成 rate() 就稳了。

4.4 知识体系总览

下面这张图总结了 PromQL 的核心知识结构,你可以把它当作一个快速参考:

PromQL 核心知识体系 PromQL 查询语言 即时查询 范围查询 聚合操作 当前时刻快照 offset 偏移 区间向量 [5m] [1h] sum / avg / max by (标签) Rate / Irate 函数 rate():平均增长率 irate():瞬时增长率 使用建议 告警用 rate(),面板用 irate() 避免 irate() 导致误告警

4.5 实战:构建一个完整的查询

光说不练假把式。我们来看一个完整的例子:监控集群的 CPU 使用率。

# 1. 原始数据:每个 CPU 核的空闲时间
node_cpu_seconds_total{mode="idle"}

# 2. 计算每个核的 CPU 使用率(1 - 空闲率)
1 - rate(node_cpu_seconds_total{mode="idle"}[5m])

# 3. 按 instance 聚合,计算每台机器的平均 CPU 使用率
avg(1 - rate(node_cpu_seconds_total{mode="idle"}[5m])) by (instance)

# 4. 只显示 CPU 使用率超过 80% 的机器
avg(1 - rate(node_cpu_seconds_total{mode="idle"}[5m])) by (instance) > 0.8

这个查询从原始数据开始,一步步加工,最终得到了我们想要的结果。我在项目中经常用这种逐步构建的方法,先在 Prometheus 的查询面板里试,确认每一步的结果都对,再放到告警规则里。

记住:PromQL 查询就像搭积木。从原始指标开始,用函数和操作符一层层加工,最终得到你想要的监控数据。不要试图一步到位,分步验证才是正道。