本篇是《Prometheus 从入门到生产实践》第 7 / 24 篇。配置以文中版本与环境前提为准,实验和故障均为教学场景。完整程序、配置及校验范围见配套实验包。

PromQL 不是给 SQL 换个写法。它首先选择“哪些时间序列”,再决定“在什么时刻、怎样使用这些样本”。理解这两层后,很多空图、类型错误和历史数据误读都能解释清楚。
1. 先知道表达式返回什么
PromQL 的常见结果类型包括标量、瞬时向量、范围向量和字符串。这里“向量”可以先理解成一组带标签的数据。
| 表达式 | 类型与含义 |
|---|---|
60 |
标量,一个数字 |
up{job="demo"} |
瞬时向量:每条匹配序列在求值时刻的一项值 |
ops_demo_requests_total{job="demo"}[5m] |
范围向量:每条序列在窗口里的多个样本 |
rate(ops_demo_requests_total{job="demo"}[5m]) |
把窗口数据转换成瞬时向量,每条序列一个速率 |
原生直方图可以作为序列样本的形式出现,不等于新增一种顶层表达式类型。很多函数对普通浮点样本与直方图样本有不同要求,不能只看函数名就任意套用。
2. 选择器先收窄目标
共享实验导出 ops_demo_requests_total,标签包含 method、route、code,Prometheus 另外加入 job、instance。先从最具体的一条查询开始:
ops_demo_requests_total{job="demo",route="/api",method="GET",code="200"}
= 精确匹配,!= 排除,=~ 与 !~ 使用正则。PromQL 标签正则按整个标签值匹配,因此 code=~"5.." 表示三位且以 5 开头;它不是在任意长字符串中寻找一个 5。
ops_demo_requests_total{job="demo",code=~"2..|5.."}
还要理解空标签值:能匹配空字符串的条件也可能匹配没有该标签的序列。不要把 environment!="production" 自动理解成“明确标记了其他环境”,它也可能包含根本没有 environment 的数据。若要求标签有值,应明确使用非空条件并核对标签规范。
查询选择器与类型定义可对照 PromQL 基础文档。
3. 瞬时查询与范围查询不是同一个概念
在某一个时刻执行表达式,是瞬时查询;在一段时间内按 step 多次求值,是范围查询。Grafana 折线通常使用后者。
表达式里的 [5m] 是每次求值向前看的样本窗口,API 的 start、end、step 则决定在哪些时刻求值。把 step 改成 1m,不会自动把 [5m] 改成 [1m],也不会改变历史抓取频率。
例如图表每 30 秒画一点,每点都可能是“此前五分钟平均速率”。相邻结果共享大量输入样本,所以曲线看起来平滑是正常的。原始范围向量通常要先经过 rate、avg_over_time 等函数,才能成为适合范围图绘制的表达式结果。
4. 时间窗口的边界和样本数
范围选择器使用左开右闭区间:恰好落在左边界的样本不算,右边界包含在内。若每分钟有一个样本,求值时间为 10:05,[2m] 通常包含 10:04 与 10:05,而不是同时包括 10:03。
抓取可能抖动、丢失,实际数量不能只靠“窗口长度除以抓取间隔”猜。可以观察:
count_over_time(ops_demo_requests_total{job="demo",code="200"}[5m])
对于 rate,窗口里至少需要两个有效样本才能计算。刚启动就查询窗口速率为空,不一定是指标名错了;先看原始指标和抓取次数,再看窗口。
5. offset、@ 与子查询分别移动哪一层
offset 相对求值时刻移动选择时间,适合与历史同周期比较:
sum(rate(ops_demo_requests_total{job="demo"}[5m] offset 1h))
它查看的是“一小时前那个五分钟窗口”,不是把当前曲线简单平移显示。实验没有一小时历史时,空结果符合预期。
@ 可固定选择器的求值时间,例如 @ 1735689600 对应一个固定 Unix 时间;只有该时间附近实际存在数据,才有结果。范围查询中的 @ start()、@ end() 可把选择时间固定到这次查询边界,适合特定对照分析。不要把截图里的时间戳长期写死在告警规则中。
子查询则让一个表达式在内层窗口里重复求值,例如:
max_over_time(sum(rate(ops_demo_requests_total{job="demo"}[5m]))[1h:1m])
意思是:内层按一分钟计算五分钟速率,再取过去一小时这些结果的最大值。它并不是“最近一小时最大的单次请求耗时”,也会比简单查询处理更多数据。
6. 空结果、零、NaN 和过期不是一回事
零是一个真实数值;空结果意味着没有返回对应序列;NaN 表示特定运算没有得到普通数值,比如 0/0。它们在图上有时都不明显,但告警语义不同。
瞬时选择器会在回看范围内寻找较新的有效样本,默认 lookback 通常为五分钟;若序列已经被标记 stale,后续求值不再把旧值当作当前值。目标移除、指标消失和抓取失败还要结合实际状态判断,不能认为“最后一个点永远持续有效”。
查询时间、lookback 与 stale 的细节以所用版本为准,见 Staleness 说明。运维时先分别查询 up、原始业务指标和 count_over_time,往往能判断问题出在采集、时间范围还是表达式。
7. 动手练习
对共享实验连续访问几次 /api,先查原始 counter,再查 [5m] 的样本数量和 rate。切换瞬时表格与时间图,写下“每个点的求值时刻”和“每个点的输入窗口”分别是什么。
再查询 offset 1h,没有历史时应接受空结果,不要用补零把它伪装成一小时前没有请求。掌握这一层时间语义,后面计算请求速率、误差和 SLO 才有可靠基础。
上一篇:服务发现与Relabeling:先弄清数据在哪一阶段改变 · 系列目录 · 下一篇:Counter 与速率:rate、increase、重置和错误率