Prometheus PromQL 查询基础:即时查询与范围查询、选择器、时间修饰符与字面量详解
【免费下载链接】prometheusThe Prometheus monitoring system and time series database.项目地址: https://gitcode.com/GitHub_Trending/pr/prometheus
本篇技术指南以 Prometheus 官方文档《Querying basics》为主体,系统讲解 PromQL(Prometheus Query Language)的数据类型、字面量、时间序列选择器、offset与@时间修饰符、子查询以及查询引擎的时间语义。读完本文,你将能够正确编写即时查询与范围查询、理解 lookback/staleness 机制的底层行为,并结合开源仓库中的解析器与引擎源码,定位查询行为背后的实现依据。
两种查询形态:即时查询与范围查询
Prometheus 提供了一套函数式查询语言 PromQL,让用户能够实时地选择与聚合时间序列数据。向 Prometheus 发送查询请求时,请求可以是:
- 即时查询(instant query):在单一时间点求值;
- 范围查询(range query):在起止时间之间以固定步长(step)间隔多次求值。
两种情形下 PromQL 的工作方式完全一致:范围查询本质上就是在不同时间戳上多次执行的即时查询。
在 Prometheus Web UI 中,"Table" 页签对应即时查询,"Graph" 页签对应范围查询。其他程序则可以通过 HTTP API 获取任意 PromQL 表达式的求值结果。
样本(Samples):浮点样本与原生直方图样本
PromQL 在某个时间戳返回的样本值,可能是浮点数(float),也可能是**原生直方图(native histogram)**样本:
- 浮点样本就是一个简单的浮点数;
- 原生直方图样本则包含一个完整的直方图,包括 count、sum 和各个桶(buckets)。
一个容易混淆的术语约定:
| 术语 | 含义 |
|---|---|
| histogram sample(直方图样本) | 在 PromQL 文档语境下,永远指原生直方图 |
| classic histogram(经典直方图) | 由_bucket、_count、_sum三个后缀的一系列时间序列组成,共同描述一个直方图 |
从 PromQL 的视角看,经典直方图里只有浮点样本,并不存在“经典直方图样本”这一说法。
浮点样本与直方图样本都可以具有 counter 或 gauge 的“flavor”(语义类型):
- 浮点样本不存储自身的 flavor,由写查询的人自行注意(约定上,包含浮点 counter 的时间序列名以
_total结尾以帮助区分); - 直方图样本“知道”自己是 counter 还是 gauge,因此引擎可以对不匹配的操作给出可靠告警。例如对 gauge 浮点序列使用
rate()函数,查询不会报错但结果很可能无意义;而对 gauge 直方图执行同样的操作,查询结果会带有警告注解(warning annotation)。
此外,向量(vector)和时间序列可以同时混合包含浮点样本与直方图样本。
表达式语言的数据类型
在 Prometheus 表达式语言中,一个表达式或子表达式可以求值为四种类型之一:
- Instant vector(即时向量)——一组时间序列的集合,每个时间序列包含一个样本,所有样本共享同一时间戳;
- Range vector(范围向量)——每个时间序列包含一段时间内的多个数据点;
- Scalar(标量)——简单的数值型浮点值;
- String(字符串)——简单的字符串值;当前尚未使用。
不同类型的合法性因使用场景而异:对于即时查询,上述任意数据类型都可以作为表达式根节点的结果;而范围查询只支持标量类型(scalar)和即时向量类型(instant vector)的表达式。
从源码结构看,这四种类型在 类型定义 中以ValueType常量(vector、scalar、matrix、string)表达,DocumentedType()函数把内部类型名映射为文档面向用户的术语(如vector→ "instant vector"、matrix→ "range vector"),可见文档术语与解析器内部术语是一一对应的。
原生直方图桶布局的对齐(Reconciliation)
原生直方图可能具有不同的桶布局(bucket layout),但在执行二元运算和聚合运算前,通常可以被转换为兼容的版本。作用于范围向量、且适用于原生直方图的函数同样会执行这种对齐:
- 二元运算中,对齐是成对(pairwise)进行的;
- 聚合运算与聚合函数中,所有直方图样本会被对齐到同一个兼容的桶布局。
并非所有桶布局都能被对齐。若操作中遇到不兼容的直方图,对应的输出向量元素会从结果中移除,并带上 warn 级别的注解(annotation)。
字面量(Literals)
PromQL 中不存在“直方图字面量”。以下是各类字面量的语法说明。
字符串字面量
字符串字面量由单引号、双引号或反引号界定。单引号与双引号字符串遵循与 Go 相同的转义规则:反斜杠开始转义序列,后跟a、b、f、n、r、t、v或\;也可用八进制(\nnn)、十六进制(\xnn、\unnnn、\Unnnnnnnn)表示特定字符。
反引号字符串不解析转义字符。注意:与 Go 不同,Prometheus不会丢弃反引号内的换行符。
"this is a string" 'these are unescaped: \n \\ \t' `these are not unescaped: \n ' " \t`浮点字面量与时间单位
标量浮点值可以写成如下格式(空格仅为提高可读性):
[-+]?( [0-9]*\.?[0-9]+([eE][-+]?[0-9]+)? | 0[xX][0-9a-fA-F]+ | [nN][aA][nN] | [iI][nN][fF] )示例:
23 -2.43 3.4e-9 0x8f -Inf NaN此外,下划线(_)可用于十进制或十六进制数字之间以提升可读性:
1_000_000 .123_456_789 0x_53_AB_F3_82浮点字面量也用于表示以秒为单位的时间长度。十进制整数可以与以下时间单位组合:
| 单位 | 含义 |
|---|---|
ms | 毫秒 |
s | 秒,1s = 1000ms |
m | 分钟,1m = 60s(忽略闰秒) |
h | 小时,1h = 60m |
d | 天,1d = 24h(忽略夏令时) |
w | 周,1w = 7d |
y | 年,1y = 365d(忽略闰日) |
给十进制整数加上单位,等价于以纯浮点字面量表示相同秒数:
1s # 等价于 1 2m # 等价于 120 1ms # 等价于 0.001 -2h # 等价于 -7200以下写法不合法:
0xABm # 十六进制数不允许加单位后缀 1.5h # 时间单位不能与浮点数组合 +Infd # ±Inf 与 NaN 不允许加单位后缀多个单位可以拼接,但必须从大到小排列,且同一单位在一个字面量中只能出现一次:
1h30m # 等价于 5400s,即 5400 12h34m56s # 等价于 45296s,即 45296 54s321ms # 等价于 54.321持续时间表达式(Duration expressions)
在需要时间持续时间的地方——即范围向量选择器和offset持续时间——都可以使用算术表达式。
在范围向量中:
rate(http_requests_total[5m * 2]) # 10 分钟范围 rate(http_requests_total[(5+2) * 1m]) # 7 分钟范围在 offset 持续时间中:
http_requests_total offset (1h / 2) # 30 分钟偏移 http_requests_total offset ((2 ^ 3) * 1m) # 8 分钟偏移使用offset加持续时间表达式时,必须用括号包裹整个表达式;不加括号的话,offset 计算只会取第一个持续时间值。
支持的运算符遵循常规优先级规则:
| 运算符 | 含义 |
|---|---|
+ | 加法 |
- | 减法 |
* | 乘法 |
/ | 除法 |
% | 取模 |
^ | 幂运算 |
持续时间表达式中还可使用以下函数:
step():解析为范围查询的步长(step width);即时查询中解析为0s;range():解析为范围查询的总时长(end time − start time);即时查询中解析为0s。与@ end()结合尤其有用,例如max_over_time(metric[range()] @ end())可以在整个查询范围内回溯;min_of(<duration>, <duration>):返回两者中较小者,用于给持续时间设置上限;max_of(<duration>, <duration>):返回两者中较大者,用于强制最小值。
例如,max_of(step(), 5s)保证持续时间不小于5s,min_of(range(), 1h)把持续时间上限封顶在1h。
注意:@修饰符中不支持持续时间表达式。
等价持续时间示例:
5m * 2等价于10m或600s;10m - 1m等价于9m或540s;(5+2) * 1m等价于7m或420s;1h / 2等价于30m或1800s;4h % 3h等价于1h或3600s;(2 ^ 3) * 1m等价于8m或480s;step() + 1等价于查询步长增加1s;max_of(step(), 5s)等价于查询步长与5s中的较大者;min_of(2 * step() + 5s, 5m)等价于“查询步长两倍加5s”与5m中的较小者。
从源码看,持续时间表达式在解析后并不立即求值,而是以 AST 节点DurationExpr形式挂在OriginalOffsetExpr/RangeExpr/StepExpr字段上(见 AST 定义),在执行前由 durationVisitor 统一计算。该访问器处理三类节点:VectorSelector(offset 表达式)、MatrixSelector(范围表达式)和SubqueryExpr(offset、step、range 三类表达式)。求值过程还包含重要的健壮性检查(见 calculateDuration):
- 显式拒绝 NaN 与 ±Inf,防止其绕过边界检查后在
time.Duration转换时产生未定义值; - 拒绝超出 int64 纳秒表示范围的持续时间(即 |seconds| 不超过 2^63/1e9);
- 除零、取模零会直接报错;
- 范围向量与子查询的范围/步长不允许为负或零,而 offset 允许负值(用于时间前向比较)。
时间序列选择器
选择器是 PromQL 的基本构件,指明查询引擎要获取哪些数据。
即时向量选择器
即时向量选择器在一给定时间戳处选择一组时间序列,并为每个序列返回一个样本值。最简单的形式只写指标名,结果是一个即时向量,包含所有该指标名的时间序列:
http_requests_total返回的值是查询求值时间戳之前最近的那个样本(即时查询对应查询时间,范围查询对应当前 step)。使用@修饰符 可以覆盖选择所依据的时间戳。只有当时间序列的最近样本距当前时刻不超过 lookback period 时,该序列才会被返回。
可以用花括号{}追加逗号分隔的标签匹配器进一步过滤:
http_requests_total{job="prometheus",group="canary"}标签匹配操作符共四个:
| 操作符 | 含义 |
|---|---|
= | 与给定字符串完全相等 |
!= | 与给定字符串不相等 |
=~ | 与给定字符串正则匹配 |
!~ | 与给定字符串正则不匹配 |
正则匹配是完全锚定的:env=~"foo"等价于env=~"^foo$"。
例如,选择staging、testing、development三种环境下、HTTP 方法非GET的所有http_requests_total时间序列:
http_requests_total{environment=~"staging|testing|development",method!="GET"}匹配空值的标签匹配器,也会选中根本没有该标签的时间序列。同一标签名可以出现多个匹配器,且全部匹配才返回结果。
给定数据集:
http_requests_total http_requests_total{replica="rep-a"} http_requests_total{replica="rep-b"} http_requests_total{environment="development"}查询http_requests_total{environment=""}会匹配并返回前三条(它们都没有environment标签),而排除http_requests_total{environment="development"}。
查询:
http_requests_total{replica!="rep-a",replica=~"rep.*"}只会匹配http_requests_total{replica="rep-b"}。
向量选择器必须指定指标名,或至少一个不匹配空字符串的标签匹配器。以下表达式非法:
{job=~".*"} # Bad!而下面两个都合法,因为它们含有不匹配空标签值的匹配器:
{job=~".+"} # Good! {job=~".*",method="get"} # Good!从源码看,这条规则由解析器在 validate 阶段强制执行:“A Vector selector must contain at least one non-empty matcher to prevent implicit selection of all metrics (e.g. by a typo)”——防止因拼写错误而隐式选中全部指标。
标签匹配器也可以作用于指标名本身,通过匹配内部__name__标签实现。例如http_requests_total等价于{__name__="http_requests_total"},且!=、=~、!~也可使用。以下表达式选中所有名字以job:开头的指标:
{__name__=~"job:.*"}指标名不得是bool、on、ignoring、group_left、group_right这些关键字之一。以下表达式非法:
on{} # Bad!变通方案是借助__name__标签:
{__name__="on"} # Good!范围向量选择器
范围向量字面量与即时向量字面量工作方式相同,但选择的是从当前时刻向前回溯一段时间内的样本序列。语法上,在向量选择器末尾用方括号[]追加一个浮点字面量,指定向前取多少秒的数据。常见写法使用带时间单位的字面量,如[5m]。
范围区间是左开右闭的:时间戳恰好在左边界上的样本被排除,时间戳恰好在右边界上的样本被包含。
示例——选择所有指标名为http_requests_total且job标签为prometheus的时间序列在 5 分钟内的所有值:
http_requests_total{job="prometheus"}[5m]offset 修饰符
offset修饰符可以改变查询中单个即时向量或范围向量的时间偏移。
例如,以下表达式返回相对于当前查询求值时刻过去 5 分钟时http_requests_total的值:
http_requests_total offset 5m注意offset修饰符必须紧跟选择器,例如这样写是对的:
sum(http_requests_total{method="GET"} offset 5m) // GOOD.而下面这种是错误的:
sum(http_requests_total{method="GET"}) offset 5m // INVALID.对范围向量同理。下面返回http_requests_total一周前的 5 分钟速率:
rate(http_requests_total[5m] offset 1w)查询历史样本时,负数 offset可以实现向未来方向的时间比较:
rate(http_requests_total[5m] offset -1w)注意:这允许查询“超前于”其求值时间向前看。
@ 修饰符
@修饰符可以改变查询中单个即时向量或范围向量的求值时间。@修饰符接受的时间值是用浮点字面量表示的Unix 时间戳。
例如,以下表达式返回2021-01-04T07:40:00+00:00时http_requests_total的值:
http_requests_total @ 1609746000与offset相同,@必须紧跟选择器。这样写是对的:
sum(http_requests_total{method="GET"} @ 1609746000) // GOOD.而下面这种是错误的:
sum(http_requests_total{method="GET"}) @ 1609746000 // INVALID.对范围向量同理,返回2021-01-04T07:40:00+00:00时刻的 5 分钟速率:
rate(http_requests_total[5m] @ 1609746000)@修饰符支持上文描述的全部数字字面量形式。它还可以与offset修饰符一起使用,此时 offset 相对于@修饰符指定的时间生效;无论两个修饰符的书写顺序如何,结果相同:
# offset 在 @ 之后 http_requests_total @ 1609746000 offset 5m # offset 在 @ 之前 http_requests_total offset 5m @ 1609746000此外,start()与end()可作为@修饰符的特殊取值:
- 对范围查询,二者分别解析为该范围查询的起始与结束时间,且对所有 step 保持不变;
- 对即时查询,
start()与end()都解析为求值时间。
http_requests_total @ start() rate(http_requests_total[5m] @ end())注意:@修饰符允许查询超前于其求值时间。
从源码结构看,@修饰符在解析器中以Timestamp *int64字段(绝对时间戳)和StartOrEnd ItemType字段(标记start()/end())承载于 VectorSelector 与 SubqueryExpr 上;Offset字段则存放执行时实际使用的偏移量,由原始 offset、@时间、求值时间与子查询偏移共同计算得出。
子查询(Subquery)
子查询允许你针对给定的时间范围与分辨率运行一次即时查询。子查询的结果是一个范围向量。
语法:
<instant_query> '[' <range> ':' [<resolution>] ']' [ @ <float_literal> ] [ offset <float_literal> ]其中<resolution>(分辨率,即子查询内部的 step)是可选的,默认取全局求值间隔。
从 AST 结构看,SubqueryExpr 同时携带Range/RangeExpr(范围)、Step/StepExpr(分辨率)与OriginalOffset/OriginalOffsetExpr(偏移),三者均可使用持续时间表达式,并在 durationVisitor 中统一求值。
运算符与函数
Prometheus 支持大量二元运算符与聚合运算符,详见表达式语言运算符页;也支持若干对数据操作的函数,详见表达式语言函数页。
注释
PromQL 支持以#开头的行注释:
# This is a comment正则表达式
Prometheus 中所有正则表达式均使用 RE2 语法(Google RE2,具有线性时间复杂度的确定性匹配特性),且正则匹配始终是全程锚定的。
常见陷阱(Gotchas)
Staleness(陈旧样本)与 lookback period
查询采样所用的时间戳独立于实际现存时间序列数据选择,这主要是为了支持聚合(sum、avg等)这类场景——被聚合的多个时间序列在时间上往往并不精确对齐。正因其独立性,Prometheus 必须为每个相关时间序列在这些时间戳上分配一个值:它的做法是取“距该时间戳不超过 lookback period 之前的最新样本”。
lookback period 默认 5 分钟,可以通过--query.lookback-delta命令行标志设置(见命令行参考),也可以在单个查询中通过lookback_delta参数覆盖。
从源码看,默认值定义于 engine.go 的defaultLookbackDelta = 5 * time.Minute;当LookbackDelta为 0 时引擎自动回退到该默认值;命令行标志在 main.go 中注册为query.lookback-delta(描述为 “The maximum lookback duration for retrieving metrics during expression evaluations and federation.”)。行为边界由 TestQueryLookbackDelta 测试用例验证:样本恰好落后一个默认 lookback(5 分钟)时会被查询取到,再落后一个毫秒则不再取到。
陈旧(stale)语义:
- 当某个 target 的抓取或规则求值不再返回先前存在的时间序列样本时,该序列会被标记为 stale;
- 当 target 被移除时,其先前抓取的时间序列也会很快被标记为 stale;
- 若查询的采样时间戳晚于序列被标记为 stale 的时刻,则不再为该序列返回值;之后若重新摄入新样本,则按预期返回;
- 序列在不再导出、或 target 不存在时变 stale,它们会从图表中最后采集样本的时刻消失,之后查询不再返回。
一个例外:某些自行打时间戳的 exporter 行为不同——其停止导出的序列会在消失前保持最后一个值(默认 5 分钟)。track_timestamps_staleness配置可以改变该行为。
避免慢查询与过载
当查询需要处理大量数据时,绘图可能会超时,甚至压垮服务器或浏览器。因此,在未知数据上构建查询时,始终先在 Prometheus 表达式浏览器的表格视图(tabular view)中构建查询,直到结果集看起来合理(最多几百条、而非几千条时间序列)。只有当数据被充分过滤或聚合后,再切换到图表模式。如果表达式即席(ad-hoc)绘图仍然太慢,就通过记录规则预先记录计算结果。
这一点在 Prometheus 中尤其关键:像api_http_requests_total这样裸的指标名选择器,可能扩展为数千条带不同标签的时间序列。同时要记住,对大量时间序列做聚合的表达式即使输出只有少数几条序列,也会给服务器带来负载——正如在关系数据库中对某一整列求和,即便输出只是一个数字,也会很慢。
小结与延伸阅读
| 主题 | 关键要点 |
|---|---|
| 即时/范围查询 | 范围查询 = 不同时间戳上的多次即时查询;Table 页签即时,Graph 页签范围 |
| 样本类型 | 浮点样本与原生直方图样本;直方图自带 counter/gauge 语义,可产生可靠性告警 |
| 四种数据类型 | instant vector、range vector、scalar、string(未用);范围查询仅支持 scalar 与 instant vector |
| 字面量 | 字符串(三种引号)、浮点(含下划线、十六进制)、时间单位(ms/s/m/h/d/w/y 从大到小拼接) |
| 持续时间表达式 | + - * / % ^与step()、range()、min_of()、max_of();offset 表达式需加括号;@中不支持 |
| 选择器 | 空值匹配器选中无该标签的序列;{job=~".*"}非法,至少需一个非空匹配器 |
offset/@ | 均须紧跟选择器;支持负 offset 与start()/end(),可超前求值时间 |
| Staleness | 默认 5 分钟 lookback,--query.lookback-delta或单查询lookback_delta可调 |
更多学习材料可参考仓库中的 示例文档(建议初学者先通过示例入门)、运算符、函数与 HTTP API 参考页;深入实现可阅读 promql/parser/parse.go(解析与校验)、promql/parser/ast.go(AST 节点定义)、promql/durations.go(持续时间表达式求值)与 promql/engine.go(查询引擎)。
【免费下载链接】prometheusThe Prometheus monitoring system and time series database.项目地址: https://gitcode.com/GitHub_Trending/pr/prometheus
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考