0x00 概述
Prometheus 提供了一种功能表达式语言 PromQL
,允许用户实时选择和汇聚时间序列数据。表达式的结果可以在浏览器中显示为图形,也可以显示为表格数据,或者由外部系统通过 HTTP API 调用。
0x01 表达式语言数据类型
在 Prometheus 的表达式语言中,表达式或子表达式包括以下四种类型之一:
-
瞬时向量(Instant vector) - 一组时间序列,每个时间序列包含单个样本,它们共享相同的时间戳。也就是说,表达式的返回值中只会包含该时间序列中的最新的一个样本值。而相应的这样的表达式称之为瞬时向量表达式。
-
区间向量(Range vector) - 一组时间序列,每个时间序列包含一段时间范围内的样本数据。
-
标量(Scalar) - 一个浮点型的数据值。
- 字符串(String) - 一个简单的字符串值。
根用户输入的表达式返回的数据类型是否合法取决于用例的不同,例如:瞬时向量表达式返回的数据类型是唯一可以直接绘制成图表的数据类型。
0x02 字面量
字符串
字符串可以用单引号、双引号或反引号指定为文字常量。
PromQL 遵循与 Go 相同的转义规则。在单引号或双引号中,用反斜杠来表示转义序列,后面可以跟 a
, b
, f
, n
, r
, t
, v
或 。特殊字符可以使用八进制(
nn
)或者十六进制(xnn
,unnnn
和 Unnnnnnnn
)。
与 Go 不同,Prometheus 不会对反引号内的换行符进行转义。
例如:
"this is a string" 'these are unescaped: \ ' `these are not unescaped: ' " `
标量
标量浮点值可以字面上写成 [-](digits)[.(digits)]
的形式。
-2.43
0x03 时间序列过滤器
瞬时向量过滤器
瞬时向量过滤器允许在指定的时间戳内选择一组时间序列和每个时间序列的单个样本值。在最简单的形式中,近指定指标(metric)名称。这将生成包含此指标名称的所有时间序列的元素的瞬时向量。
例如:选择指标名称为 http_requests_total
的所有时间序列:
http_requests_total
可以通过向花括号({}
)里附加一组标签来进一步过滤时间序列。
例如:选择指标名称为 http_requests_total
,job
标签值为 prometheus
,group
标签值为 canary
的时间序列:
http_requests_total{job="prometheus",group="canary"}
PromQL 还支持用户根据时间序列的标签匹配模式来对时间序列进行过滤,目前主要支持两种匹配模式:完全匹配和正则匹配。总共有以下几种标签匹配运算符:
-
=
: 选择与提供的字符串完全相同的标签。 -
!=
: 选择与提供的字符串不相同的标签。 -
=~
: 选择正则表达式与提供的字符串(或子字符串)相匹配的标签。 -
!~
: 选择正则表达式与提供的字符串(或子字符串)不匹配的标签。
例如:选择指标名称为 http_requests_total
,环境为 staging
、testing
或 development
,HTTP 方法为 GET
的时间序列:
http_requests_total{environment=~"staging|testing|development",method!="GET"}
没有指定标签的标签过滤器会选择该指标名称的所有时间序列。
所有的 PromQL 表达式必须至少包含一个指标名称,或者一个不会匹配到空字符串的标签过滤器。
以下表达式是非法的(因为会匹配到空字符串):
{job=~".*"} # 非法!
以下表达式是合法的:
{job=~".+"} # 合法! {job=~".*",method="get"} # 合法!
除了使用 <metric name>{label=value}
的形式以外,我们还可以使用内置的 __name__
标签来指定监控指标名称。例如:表达式 http_requests_total
等效于 {__name__="http_requests_total"}
。也可以使用除 =
之外的过滤器(=
,=~
,~
)。以下表达式选择指标名称以 job:
开头的所有指标:
{__name__=~"job:.*"}
Prometheus 中的所有正则表达式都使用 RE2语法。
0x04 区间向量过滤器
区间向量与瞬时向量的工作方式类似,唯一的差异在于在区间向量表达式中我们需要定义时间选择的范围,时间范围通过时间范围选择器 []
进行定义,以指定应为每个返回的区间向量样本值中提取多长的时间范围。
时间范围通过数字来表示,单位可以使用以下其中之一的时间单位:
-
s
- 秒 -
m
- 分钟 -
h
- 小时 -
d
- 天 -
w
- 周 -
y
- 年
例如:选择在过去 5 分钟内指标名称为 http_requests_total
,job
标签值为 prometheus
的所有时间序列:
http_requests_total{job="prometheus"}[5m]
0x05 时间位移操作
在瞬时向量表达式或者区间向量表达式中,都是以当前时间为基准:
http_request_total{} # 瞬时向量表达式,选择当前最新的数据 http_request_total{}[5m] # 区间向量表达式,选择以当前时间为基准,5分钟内的数据
而如果我们想查询,5 分钟前的瞬时样本数据,或昨天一天的区间内的样本数据呢? 这个时候我们就可以使用位移操作,位移操作的关键字为 offset
。
例如,以下表达式返回相对于当前查询时间过去 5 分钟的 http_requests_total
值:
http_requests_total offset 5m
注意:offset
关键字需要紧跟在选择器({}
)后面。以下表达式是正确的:
sum(http_requests_total{method="GET"} offset 5m) #合法
下面的表达式是不合法的:
sum(http_requests_total{method="GET"}) offset 5m # 错误
该操作同样适用于区间向量。以下表达式返回指标 http_requests_total
一周前的 5 分钟之内的 HTTP 请求量的增长率:
rate(http_requests_total[5m] offset 1w)
0x06 操作符
使用PromQL除了能够方便的按照查询和过滤时间序列以外,PromQL还支持丰富的操作符,用户可以使用这些操作符对进一步的对事件序列进行二次加工。这些操作符包括:数学运算符,逻辑运算符,布尔运算符等等。详细描述请参考 PromQL 操作符。
0x07 内置函数
Prometheus 提供了大量的内置函数来处理时序数据,详细描述请参考 PromQL 内置函数。
0x08 陷阱
失效
执行查询操作时,独立于当前时刻被选中的时间序列数据所对应的时间戳,这个时间戳主要用来进行聚合操作,包括 sum
, avg
等,大多数聚合的时间序列数据所对应的时间戳没有对齐。由于它们的独立性,我们需要在这些时间戳中选择一个时间戳,并已这个时间戳为基准,获取小于且最接近这个时间戳的时间序列数据。
如果采样目标或告警规则不再返回之前存在的时间序列的样本,则该时间序列将被标记为失效。如果删除了采样目标,则之前返回的时间序列也会很快被标记为失效。
如果在某个时间序列被标记为失效后在该时间戳处执行查询操作,则不会为该时间序列返回任何值。如果随后在该时间序列中插入了新的样本,则照常返回时间序列数据。
如果在采样时间戳前 5 分钟(默认情况)未找到任何样本,则该时间戳不会返回任何任何该时间序列的值。这实际上意味着你在图表中看到的数据都是在当前时刻 5 分钟前的数据。
对于在采样点中包含时间戳的时间序列,不会被标记为失效。在这种情况下,仅使用 5 分钟阈值检测的规则。
避免慢查询和高负载
如果一个查询需要操作非常大的数据量,图表绘制很可能会超时,或者服务器负载过高。因此,在对未知数据构建查询时,始终需要在 Prometheus 表达式浏览器的表格视图中构建查询,直到结果是看起来合理的(最多为数百个,而不是数千个)。只有当你已经充分过滤或者聚合数据时,才切换到图表模式。如果表达式的查询结果仍然需要很长时间才能绘制出来,则需要通过记录规则重新清洗数据。
像 api_http_requests_total
这样简单的度量指标名称选择器,可以扩展到具有不同标签的数千个时间序列中,这对于 Prometheus 的查询语言是非常重要的。还要记住,对于聚合操作来说,即使输出的时间序列集非常少,它也会在服务器上产生负载。这类似于在关系型数据库中查询一个字段的总和,总是非常缓慢。