4. PromQL 入门:数据类型、选择器、操作符、常用函数与聚合

好,咱们进入 PromQL 的世界。说实话,PromQL 是 Prometheus 监控的灵魂。你指标采集得再好,告警规则写得再全,不会查 PromQL,等于白搭。我见过太多运维同学,对着 Prometheus 的 UI 界面发呆,想查个东西不知道从哪下手。今天咱们就把这块彻底讲透。

4.1 数据类型:就四种,别怕

PromQL 的数据类型,说白了就四种。你记住它们,后面就好办了。

类型 说明 我常用的场景
瞬时向量 某个时间点的一组时间序列 查当前 CPU 使用率、内存占用
区间向量 一段时间内的一组时间序列 看过去 5 分钟的流量趋势
标量 一个单纯的数字 比如阈值 0.8、告警级别 3
字符串 文本值,用得少 偶尔在告警描述里用一下

嗯,这里要注意:瞬时向量是你最常用的。比如 node_cpu_seconds_total 查出来就是一堆带标签的时间序列,每个序列在当前时间点有一个值。而区间向量呢,就是给这个查询加个时间范围,比如 node_cpu_seconds_total[5m],返回的是过去 5 分钟每个时间点的数据。

我的小习惯:刚开始学的时候,你可以在 Prometheus 的 Graph 页面里,先查瞬时向量看看当前值,再切到 Console 模式看原始数据。这样对比着学,理解快很多。

4.2 选择器:怎么精准找到你要的数据

选择器就是你的「数据过滤器」。我刚开始用的时候,经常因为标签写错一个字母,查出来空数据,排查半天。

4.2.1 精确匹配

最基础的,用 =!=

# 查某个实例的 CPU 使用率
node_cpu_seconds_total{instance="192.168.1.1:9100", mode="idle"}

# 排除某个实例
node_cpu_seconds_total{instance!="192.168.1.1:9100"}

4.2.2 正则匹配

这个我项目中用得特别多。比如你要查所有 web 服务的指标:

# 匹配以 "web-" 开头的服务
http_requests_total{service=~"web-.*"}

# 排除非生产环境
http_requests_total{env!~"dev|test"}
我曾经踩过的坑:正则匹配在数据量大的时候,性能会明显下降。有一次我在一个百万级时间序列的集群上,用了一个特别复杂的正则,结果查询直接超时。后来我改成用精确匹配 + 合理设计标签,问题就解决了。所以我的建议是:能用 = 就别用 =~

4.3 操作符:加减乘除与比较

操作符这块,说白了就是数学运算。但 PromQL 的操作符有个特点——它是向量之间的运算,不是简单的数字运算。

4.3.1 算术操作符

加减乘除、取模、幂运算,这些跟普通数学一样:

# 计算 CPU 使用率(非空闲时间占比)
1 - (node_cpu_seconds_total{mode="idle"} / node_cpu_seconds_total)

# 计算内存使用率
node_memory_MemTotal_bytes - node_memory_MemFree_bytes

4.3.2 比较操作符

这个在告警规则里是核心。你想想看,告警不就是「当某个值大于阈值时触发」吗?

# 找出 CPU 使用率超过 80% 的实例
(1 - (node_cpu_seconds_total{mode="idle"} / node_cpu_seconds_total)) > 0.8

4.3.3 逻辑操作符

andorunless 这三个,我用的最多的是 and

# 同时满足 CPU 高且内存高的实例
(CPU使用率 > 0.8) and (内存使用率 > 0.8)
重点:向量匹配是 PromQL 的难点。两个向量做运算时,必须标签完全一致才能匹配上。如果标签对不上,可以用 on()ignoring() 来指定匹配规则。这个我后面会专门讲。

4.4 常用函数:这些你必须会

PromQL 的函数很多,但常用的就那几个。我按使用频率给你排个序:

4.4.1 rate() 和 irate()

这两个是处理 Counter 类型指标的利器。Counter 是只增不减的,比如请求总数、错误总数。你要看「每秒变化率」,就得用它们。

# 每秒请求数(推荐用 rate)
rate(http_requests_total[5m])

# 瞬时请求率(对突发敏感时用)
irate(http_requests_total[5m])

我个人习惯用 rate(),因为它平滑了数据,告警不容易抖动。但如果你需要快速响应突发流量,irate() 更合适。

4.4.2 increase()

这个函数返回的是「一段时间内的增量」。比如你想知道过去 1 小时有多少请求:

increase(http_requests_total[1h])

4.4.3 histogram_quantile()

这个函数用来计算百分位数,比如 P99 响应时间。我在做性能分析时必用:

# 计算过去 5 分钟的 P99 响应时间
histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m]))
避坑指南:我曾经在计算 P99 时,发现结果总是偏大。排查了半天,原来是 bucket 设置不合理——最小的 bucket 是 0.1 秒,但大部分请求都在 0.01 秒以内。所以 histogram 的 bucket 一定要覆盖你的实际数据分布。

4.5 聚合操作:从全局看问题

单个实例的数据只能看到局部,聚合才能看到全局。PromQL 的聚合操作符,说白了就是「分组统计」。

4.5.1 常用聚合操作符

操作符 作用 我常用的场景
sum() 求和 所有实例的总请求数
avg() 平均值 集群的平均 CPU 使用率
max() 最大值 找出最忙的实例
min() 最小值 找出最空闲的实例
count() 计数 有多少个实例在运行
topk() 取前 K 个 找出请求量最高的 5 个服务

4.5.2 配合 by() 分组

这才是聚合的精髓。不加 by(),所有数据揉在一起。加了 by(),按标签分组统计:

# 按 service 分组,统计每个服务的总请求数
sum(rate(http_requests_total[5m])) by (service)

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

4.5.3 without() 排除标签

without()by() 的反向操作。它说「除了这些标签,其他都保留」:

# 排除 instance 标签,按其他所有标签聚合
sum(rate(http_requests_total[5m])) without (instance)
我的经验:刚开始用聚合时,我经常搞混 by()without()。后来我记住一个原则:by() 是「我要按什么分组」,without() 是「我不要什么标签」。你想想看,哪个更符合你的思维习惯就用哪个。

4.6 实战:一个完整的查询示例

咱们来一个综合的例子。假设你要监控一个微服务集群,想知道「过去 5 分钟,每个服务的 P99 响应时间,并且只关注那些请求量超过 100 QPS 的服务」:

# 第一步:计算每个服务的 P99 响应时间
histogram_quantile(0.99,
  sum(rate(http_request_duration_seconds_bucket[5m])) by (service, le)
)

# 第二步:计算每个服务的 QPS
sum(rate(http_requests_total[5m])) by (service)

# 第三步:组合起来,只显示 QPS > 100 的服务
(
  histogram_quantile(0.99,
    sum(rate(http_request_duration_seconds_bucket[5m])) by (service, le)
  )
)
and
(
  sum(rate(http_requests_total[5m])) by (service) > 100
)

这个查询看起来复杂,但拆开看就清楚了。先算 P99,再算 QPS,最后用 and 取交集。我在实际项目中,经常用这种「分步组合」的方式来写复杂查询。

最后一个小建议:PromQL 的学习,没有捷径。你得多查、多试、多踩坑。我建议你搭建一个测试环境,导入一些模拟数据,然后对着文档一个个函数试。等你把常用的十几个函数用熟了,PromQL 就不再是障碍了。