4. PromQL 查询语言:基础语法、常用函数与聚合操作实战

说实话,PromQL 是我在监控系统里打交道最多的东西。刚开始接触时觉得它语法怪怪的,跟 SQL 完全不是一个路子。但用顺手之后你会发现,它其实特别适合处理时序数据。今天我就带你把这门语言的核心用法捋一遍。

4.1 PromQL 基础语法:先搞懂数据类型

PromQL 里有四种数据类型,我习惯把它们分成两类:即时向量区间向量是最常用的,标量和字符串反而用得少。

类型 说明 示例
即时向量 某一时刻的一组时间序列 node_cpu_seconds_total
区间向量 一段时间内的一组时间序列 node_cpu_seconds_total[5m]
标量 一个单纯的数字 100
字符串 文本值,基本不用 "production"

你想想看,查询一个指标不加时间范围,返回的就是即时向量。加上 [5m] 就变成了区间向量。这个区别特别重要,因为很多函数只接受区间向量作为参数。

核心概念:PromQL 的查询结果永远是一组带标签的时间序列。标签就是你的维度,比如 instancejobdevice 等等。

4.2 常用函数:rate、increase、histogram_quantile

这三个函数是我在生产环境里用得最多的。说白了,它们解决了监控中最常见的三个问题:速率、增量、分位数

4.2.1 rate():算速率,看趋势

rate() 函数计算的是每秒平均增长率。它只适用于 Counter 类型的指标,比如请求数、CPU 时间、网络流量。

# 过去5分钟内,每秒平均的CPU使用时间
rate(node_cpu_seconds_total[5m])

# 过去1小时内,每秒平均的HTTP请求数
rate(http_requests_total[1h])

我个人习惯用 rate() 而不是 irate()。虽然 irate() 反应更快,但容易受采样抖动影响。有一次我在排查线上问题,用 irate() 看到的曲线像心电图一样乱跳,换成 rate() 后趋势就清晰多了。

小技巧rate() 的时间窗口至少要是采集间隔的4倍。比如你的 Prometheus 每15秒采集一次,那窗口至少用1分钟。否则数据会不准确。

4.2.2 increase():算增量,看总量

increase() 返回的是区间向量在指定时间范围内的总增量。它跟 rate() 的关系很简单:increase = rate * 时间范围秒数

# 过去24小时内,总共处理了多少请求
increase(http_requests_total[24h])

# 过去1小时内,磁盘写入的总字节数
increase(node_disk_written_bytes_total[1h])

嗯,这里要注意:increase() 在 Counter 重置时能自动处理。比如服务器重启了,计数器归零,PromQL 会自动补偿这个跳变。我曾经见过有人自己写脚本算增量,结果重启后数据全乱了——用 increase() 就省心多了。

4.2.3 histogram_quantile():算分位数,看分布

这个函数有点绕,但特别有用。它计算的是百分位值,比如 P99 延迟是多少毫秒。

# 过去5分钟内,P99的请求延迟
histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m]))

为什么用 rate() 包一层?因为 Histogram 的 bucket 是累积计数器,我们需要先算出每秒的增量,再算分位数。这是标准写法,别搞反了。

避坑指南:我曾经在配置 Histogram 时,bucket 范围设得太宽。比如延迟从 0.1s 直接跳到 10s,中间没有细粒度 bucket。结果 P99 算出来永远是 10s,根本看不出真实情况。记住:bucket 的粒度决定了分位数的精度

4.3 聚合操作实战:从原始数据到业务视图

原始指标往往太细了。比如 node_cpu_seconds_total 会按 CPU 核、模式(user/system/idle)拆成几十条序列。我们需要聚合才能看到整体情况。

4.3.1 常用聚合操作符

操作符 作用 示例
sum 求和 sum(rate(http_requests_total[5m]))
avg 求平均 avg(node_load1)
max 最大值 max(node_memory_MemFree_bytes)
min 最小值 min(node_filesystem_free_bytes)
count 计数 count(up == 1)
topk 取前K个 topk(3, rate(http_requests_total[5m]))

4.3.2 实战:按维度聚合

聚合时可以用 bywithout 来控制保留哪些标签。我更喜欢用 by,因为它更直观——你明确告诉 PromQL 要按什么维度分组。

# 按 instance 聚合 CPU 使用率
sum(rate(node_cpu_seconds_total{mode!="idle"}[5m])) by (instance)

# 按 job 和 instance 聚合请求数
sum(rate(http_requests_total[5m])) by (job, instance)

# 排除 instance 标签,按其他标签聚合
sum(rate(http_requests_total[5m])) without (instance)

你想想看,如果不加 bysum 会把所有序列加成一个数字。加了 by (instance),就能看到每台机器的独立情况。这在排查故障时特别有用——一眼就能看出是哪台机器拖了后腿。

4.3.3 实战:计算 CPU 使用率

这是最经典的 PromQL 查询,没有之一。我几乎每天都会用到它。

# 每台机器的 CPU 使用率(排除 idle 模式)
100 - (avg(rate(node_cpu_seconds_total{mode="idle"}[5m])) by (instance) * 100)

为什么用 avg 而不是 sum?因为多核 CPU 的 idle 时间加起来会超过 100%。用 avg 取平均,得到的就是整体 CPU 的空闲比例。再用 100 减去它,就是使用率。

实战经验:我在一次线上事故中,发现某台机器的 CPU 使用率突然飙到 95%。用 topk(5, rate(node_cpu_seconds_total{mode!="idle"}[5m])) by (mode) 一看,原来是 softirq 模式占了 80%。顺着这个线索,很快定位到网卡中断风暴的问题。

4.4 避坑指南与个人经验

  • 时间窗口别太小[1m] 在采集间隔 15s 时只有 4 个样本点,算出来的 rate 很不稳定。我一般用 [5m] 起步。
  • 注意 Counter 重置:虽然 PromQL 会自动处理,但如果你的 Counter 频繁重置(比如每几分钟重启一次服务),rate() 的结果会偏低。这时候考虑用 increase() 看总量更靠谱。
  • Histogram 的 bucket 要合理:我见过有人把 bucket 设成 [0.1, 0.5, 1, 5, 10, +Inf],结果 P50 和 P99 算出来都是 0.1s,完全没区分度。建议在关键区间多设几个 bucket。
  • 聚合时注意标签爆炸:如果按 instancedevicemountpoint 三个维度聚合,结果序列数会成倍增长。小心你的 Grafana 面板加载变慢。

好了,PromQL 的核心用法就这些。说白了,它就是一套专门为时序数据设计的查询语言。多写多练,慢慢就能找到感觉。下一章我们会聊 Prometheus 的告警规则配置,到时候这些查询技巧都能用上。