PromQL(Prometheus Query Language)是 Prometheus 监控系统的查询语言,也是整个云原生监控体系的核心。无论是构建 Grafana 仪表盘、编写告警规则,还是进行故障排查时的临时查询,都离不开 PromQL。我将从数据模型出发,逐步深入到聚合操作、常用函数和实战查询,最后覆盖子查询等高级技巧。

参考来源:Prometheus 官方文档 — Querying basics

一、PromQL 数据模型

PromQL 有四种基本数据类型,理解它们是写对查询的前提:

类型说明示例
即时向量(Instant Vector)一组时间序列在当前时刻的采样值node_cpu_seconds_total
范围向量(Range Vector)一组时间序列在过去一段时间内的所有采样值node_cpu_seconds_total[5m]
标量(Scalar)一个简单的数值3.141024
字符串(String)字符串值(较少使用)"hello"

最常用的两种:

  • 即时向量:仪表盘和告警中最常见,返回"当前这一刻"各序列的值。
  • 范围向量:用于 rate()increase() 等函数计算,必须带时间窗口 [...]
# 即时向量:返回当前所有序列
up

# 范围向量:返回过去5分钟内的所有采样点
up[5m]

# 标量
1 - 0.3

二、基础查询

2.1 Metric 选择与标签过滤

通过标签选择器可以精确过滤目标序列:

# 选择名为 node_cpu_seconds_total 的所有序列
node_cpu_seconds_total

# 按 mode 标签过滤
node_cpu_seconds_total{mode="idle"}

# 多标签组合(AND 关系)
node_cpu_seconds_total{instance="node-1:9100", mode="idle"}

# 标签正则匹配
node_cpu_seconds_total{instance=~"node-[0-9]+:9100"}

# 标签反向匹配(排除某些值)
node_cpu_seconds_total{mode!="idle"}

# 正则反向匹配
node_memory_MemTotal_bytes{instance!~"localhost.*"}

2.2 区间向量

在指标名后加 [时间窗口] 即可获得范围向量,时间单位支持 s(秒)、m(分钟)、h(小时)、d(天)、w(周)、y(年):

# 过去 5 分钟的采样点
http_requests_total[5m]

# 过去 1 小时
http_requests_total[1h]

# 过去 30 秒
http_requests_total[30s]

三、聚合操作

聚合操作用于对多组时间序列进行汇总计算。核心语法:

<aggr-op>([parameter,] <vector>) [without|by (<label list>)]

常用聚合算子

算子说明
sum求和
avg平均值
max / min最大值 / 最小值
count计数
count_values按值分组计数
topk / bottomk前 K / 后 K
quantile分位数

by 与 without

by 保留指定标签做分组,without 移除指定标签后对剩余标签分组:

# 按 instance 分组求 CPU 各模式的总和
sum by (instance) (node_cpu_seconds_total{mode="idle"})

# 移除 mode 和 cpu 标签后聚合(保留 instance、job 等)
sum without (cpu, mode) (node_cpu_seconds_total)

# 求每台机器 CPU 使用率最高的 top 3
topk(3, sum by (instance) (rate(node_cpu_seconds_total{mode!="idle"}[5m])))

topk 示例

# 流量最高的前 5 个 endpoint
topk(5, sum by (handler) (rate(http_requests_total[5m])))

四、常用函数

4.1 rate / irate / increase

这三个函数都只能作用于计数器(Counter)类型指标:

# rate:计算过去 5 分钟的平均增长率(推荐用于仪表盘和告警)
rate(http_requests_total[5m])

# irate:取最近两个采样点计算瞬时增长率(适合高精度短窗口图表)
irate(http_requests_total[5m])

# increase:计算过去 5 分钟的绝对增量
increase(http_requests_total[5m])

选择建议

  • rate() 适合告警和仪表盘,对数据抖动做了平滑处理。
  • irate() 适合超高精度短窗口(如 [1m]),但对数据缺失敏感。
  • increase() 用于回答"过去 1 小时总共增加了多少"这类问题。

4.2 histogram_quantile

直方图分位数计算,用于 P50/P90/P99 延迟分析:

# 计算 P99 延迟(单桶写法)
histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m]))

# 多实例场景:先按 le 聚合再计算
histogram_quantile(
  0.99,
  sum by (le, instance) (rate(http_request_duration_seconds_bucket[5m]))
)

注意:如果多个实例都暴露了同名 histogram,必须先按 le 聚合,否则 histogram_quantile 会在单个序列内查找所有桶,结果错误。

4.3 predict_linear

基于线性回归预测未来趋势,适合容量预测告警:

# 预测 1 小时后的磁盘使用量
predict_linear(node_filesystem_avail_bytes[1h], 4 * 3600)

# 磁盘将在 4 小时内写满
predict_linear(node_filesystem_avail_bytes[2h], 4 * 3600) < 0

4.4 其他高频函数

# 时间聚合:过去 1 小时每 5 分钟最大值
max_over_time(up[1h:5m])

# 一天前的同时间点
rate(http_requests_total[5m] offset 1d)

# 求百分比:已用内存占总内存的比例
1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)

# clamp_max 限制上限(如过滤异常值)
clamp_max(rate(http_requests_total[5m]), 1000)

五、实战查询示例

5.1 CPU 使用率

# 单机 CPU 使用率(%)
100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)

# 所有机器 CPU 使用率 Top 5
topk(5,
  100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)
)

原理:idle 时间占比的补数就是 CPU 使用率,使用 avg 是因为 Node Exporter 按 CPU 核心分别暴露数据。

5.2 内存使用率

# 内存使用率(%)
(node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes)
  / node_memory_MemTotal_bytes * 100

# 按主机分组
100 * (1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)

5.3 P99 延迟

# 全局 P99 延迟(秒)
histogram_quantile(0.99,
  sum by (le) (rate(http_request_duration_seconds_bucket[5m]))
)

# 按 endpoint 分组的 P99 延迟
histogram_quantile(0.99,
  sum by (le, handler) (rate(http_request_duration_seconds_bucket[5m]))
)

5.4 错误率

# HTTP 5xx 错误率(%)
sum(rate(http_requests_total{status=~"5.."}[5m]))
  / sum(rate(http_requests_total[5m])) * 100

# 按服务分组
sum by (service) (rate(http_requests_total{status=~"5.."}[5m]))
  / sum by (service) (rate(http_requests_total[5m])) * 100

5.5 综合示例:多维度流量看板查询

# 总 QPS
sum(rate(http_requests_total[5m]))

# 按 status code 分类的 QPS
sum by (status) (rate(http_requests_total[5m]))

# 成功率(2xx + 3xx 占比)
sum(rate(http_requests_total{status=~"[23].."}[5m]))
  / sum(rate(http_requests_total[5m]))

六、高级技巧

6.1 子查询

子查询允许在任意即时查询表达式上施加范围和评估步长,语法为 <expr>[range:resolution]

# 过去 1 小时内每 5 分钟的最大 CPU 使用率
max_over_time(
  100 - avg(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100
)[1h:5m]

# 计算过去 1 小时内每分钟错误率的 5 分钟滑动平均
avg_over_time(
  (sum(rate(http_requests_total{status=~"5.."}[5m]))
    / sum(rate(http_requests_total[5m])))[1h:1m]
)

6.2 offset 修饰符

offset 用于将查询时间点回拨,常用于环比分析:

# 当前 QPS
sum(rate(http_requests_total[5m]))

# 一周前同时段的 QPS
sum(rate(http_requests_total[5m] offset 1w))

# QPS 周同比
sum(rate(http_requests_total[5m]))
  - sum(rate(http_requests_total[5m] offset 1w))

6.3 @modifier(时间修饰符)

@ 修饰符可以将查询锚定到 UNIX 时间戳指定的绝对时间:

# 查询 UNIX 时间戳 1780000000 处的 CPU 使用率
node_cpu_seconds_total @ 1780000000

# 锚定到 2 小时前
rate(http_requests_total[5m] @ (time() - 2 * 3600))

# 结合 offset 使用
rate(http_requests_total[5m] @ (time() - 86400) offset 1h)

@ 修饰符从 Prometheus v2.25 起支持,适合构建"故障时间点回溯"查询。

6.4 记录规则(Recording Rules)

高频查询应使用记录规则预计算,避免每次查询都执行复杂表达式:

# prometheus-rules.yaml
groups:
  - name: custom_rules
    interval: 30s
    rules:
      - record: job:http_requests:rate5m
        expr: sum by (job) (rate(http_requests_total[5m]))

      - record: job:http_errors:ratio
        expr: |
          sum by (job) (rate(http_requests_total{status=~"5.."}[5m]))
          /
          sum by (job) (rate(http_requests_total[5m]))          

      - record: instance:cpu_usage:ratio
        expr: 1 - avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m]))

在告警和仪表盘中直接引用预计算指标 job:http_requests:rate5m,可显著降低 Prometheus 查询负载。

七、常见陷阱

  1. Counter 重置rate()increase() 会自动处理 Counter 重置(如服务重启归零),但前提是你使用的是 Counter 类型指标而非 Gauge。
  2. rate 窗口过短[1m] 窗口只有 2 个采样点时,rate 结果抖动严重,建议至少 [5m]
  3. histogram_quantile 缺少聚合:多实例场景下未先按 le 聚合直接调用,导致计算错误。
  4. 除零问题:分母可能为零时使用 clamp_min 保护:
# 安全的比率计算
sum(rate(http_requests_total{status=~"5.."}[5m]))
  / clamp_min(sum(rate(http_requests_total[5m])), 1)

总结

PromQL 的学习路径可以概括为:理解数据模型 → 掌握标签过滤 → 熟练使用聚合 → 用好核心函数(rate / histogram_quantile)→ 进阶子查询与记录规则。日常 SRE 工作中 80% 的查询场景都围绕 CPU、内存、延迟、错误率展开,把这些实战查询模板化、沉淀为记录规则,就能高效构建监控体系。

更多详情请参阅 Prometheus 官方文档 — Querying

参考资料与致谢

本文在撰写过程中参考了以下资料,感谢原作者的贡献:

  1. Prometheus 官方文档 — Querying basics — Prometheus 官方,参考了Prometheus 官方文档 — Querying basics相关内容