PromQL学习
# 一、概述
PromQL 是 Prometheus 监控系统内置的一种查询语言,PromQL 允许我们以灵活的方式选择、聚合等其他方式转换和计算时间序列数据,该语言仅用于读取数据。可以说 PromQL 是我们学习 Prometheus 最困难也是最重要的部分,Prometheus是一种开源的监控和警报系统,广泛用于云原生和容器化环境中。
# 二、基础介绍
# 2.1 概念介绍
时间序列(Time Series): Prometheus中的数据由时间序列组成。时间序列是一个具有时间戳和相应值的有序数据集。每个时间序列由指标名称和一组标签组成。
查询基本语法:PromQL具有简洁而强大的查询语法。你可以使用
metric_name
来选择指定指标名称的时间序列,也可以使用大括号{}
来选择具有特定标签组合的时间序列。例如:
http_requests_total
:选择名为http_requests_total
的所有时间序列。http_requests_total{method="GET", status="200"}
:选择方法为GET
且状态为200
的时间序列。
范围选择器(Range Vector Selectors):PromQL中的范围选择器允许我们选择一段时间范围内的时间序列数据。范围选择器使用方括号
[]
表示,其中可以指定时间范围和间隔。例如:
http_requests_total[5m]
:选择过去5分钟内的http_requests_total
时间序列数据。http_requests_total[1h]
:选择过去1小时内的http_requests_total
时间序列数据。
聚合函数(Aggregation Functions):PromQL提供了多种聚合函数,用于计算时间序列数据的统计信息或生成新的时间序列。
例如:
sum()
:计算时间序列值的总和。avg()
:计算时间序列值的平均值。max()
:找到时间序列值的最大值。
可以将聚合函数与范围选择器结合使用,以获取在指定时间范围内的聚合结果。
例如:
sum(http_requests_total[5m])
:计算过去5分钟内的http_requests_total
时间序列值的总和。
# 2.2 数据类型
标量(Scalar):标量是指一个单独的数值,它可以是整数或浮点数。在Prometheus中,标量通常用于表示指标的具体值。
时间序列(Time Series):时间序列是由一系列带有时间戳的数据点组成的序列。每个数据点都有一个时间戳和相应的数值。时间序列由指标名称和一组标签标识。
例如,
http_requests_total{method="GET", status="200"}
表示一个具有标签method="GET"
和status="200"
的http_requests_total
时间序列。瞬时向量(Instant Vector):瞬时向量是在某个时间点上的一组时间序列的集合。它可以通过指标名称和可选的标签筛选条件来选择特定的时间序列。
例如,
http_requests_total{job="webserver"}
表示在特定时间点上属于job="webserver"
的http_requests_total
时间序列集合。范围向量(Range Vector):范围向量是在一段时间范围内的一组时间序列的集合。它用于进行范围查询和聚合操作。
例如,
http_requests_total[5m]
表示在过去5分钟内的http_requests_total
时间序列集合。
# 2.3 过滤器
在PromQL中,我们可以使用过滤器来对时间序列进行筛选和过滤,以获取特定条件下的数据。过滤器通常使用大括号 {}
内的表达式来指定筛选条件。
- 根据标签筛选
http_requests_total{method="GET"} // 选择 method 标签为 GET 的时间序列
cpu_usage{instance=~"webserver.*"} // 选择 instance 标签匹配正则表达式 "webserver.*" 的时间序列
2
- 使用比较操作符筛选
temperature > 30 // 选择温度大于 30 的时间序列
error_count != 0 // 选择错误计数不等于 0 的时间序列
2
- 使用布尔逻辑筛选
http_requests_total{method="GET", status="200"} and cpu_usage > 80 // 同时满足两个条件的时间序列
http_requests_total{method="POST"} or http_requests_total{method="PUT"} // 满足其中一个条件的时间序列
2
- 使用正则表达式筛选
http_requests_total{path=~"/api/v[0-9]+/.*"} // 选择 path 标签匹配正则表达式 "/api/v[0-9]+/.*" 的时间序列
# 2.4 操作符
算术操作符:
+
:加法-
:减法*
:乘法/
:除法%
:取模(取余数)
比较操作符:
==
:等于!=
:不等于<
:小于>
:大于<=
:小于等于>=
:大于等于
布尔逻辑操作符:
and
、&&
:逻辑与or
、||
:逻辑或unless
:逻辑非
正则表达式操作符:
=~
:匹配正则表达式!~
:不匹配正则表达式
# 三、常用函数介绍
# 3.1 rate
计算时间序列值的变化速率。它通过在指定的时间范围内计算时间序列值的差异来估算速率。
rate() 函数用于计算在指定时间范围内计数器平均每秒的增加量。因为是计算一个时间范围内的平均值,所以我们需要在序列选择器之后添加一个范围选择器。
# 计算指标在最近五分钟内的每秒平均变化率
rate(demo_api_request_duration_seconds_count[5m])
2
3
4
- 瞬时计算方式
rate函数说明
- 当被抓取指标进的程重启时,
Counter
指标可能会重置为 0,但rate()
函数会自动处理这个问题,它会假设Counter
指标的值只要是减少了就认为是被重置了,然后它可以调整后续的样本,例如,如果时间序列的值为[5,10,4,6]
,则将其视为[5,10,14,16]
。 - 变化率是从指定的时间范围下包含的样本进行计算的,需要注意的是这个时间窗口的边界并不一定就是一个样本数据,可能会不完全对齐,所以,即使对于每次都是增加整数的
Counter
,也可能计算结果是非整数。 rate()
函数需要在指定窗口下至少有两个样本才能计算输出。一般来说,比较好的做法是选择范围窗口大小至少是抓取间隔的4
倍,这样即使在遇到窗口对齐或抓取故障时也有可以使用的样本进行计算,例如,对于 1 分钟的抓取间隔,你可以使用 4 分钟的 Rate 计算,但是通常将其四舍五入为 5 分钟。所以如果使用query_range
区间查询,例如在绘图中,那么范围应该至少是步长的大小,否则会丢失一些数据。
# 3.2 irate
irate类似rate函数,但是由于rate取值问题,会收到首尾值相差较大问题,这样就会导致结果不是很准确,为了解决这个问题,PromQL 提供了另外一个灵敏度更高的函数irate(v range-vector)
。irate 同样用于计算区间向量的计算率,但是其反应出的是瞬时增长率。
irate 函数是通过区间向量中最后两个样本数据来计算区间向量的增长速率。这种方式可以避免在时间窗口范围内的长尾问题,并且体现出更好的灵敏度,通过 irate 函数绘制的图标能够更好的反应样本数据的瞬时变化状态。那既然是使用最后两个点计算,那为什么还要指定类似于 [1m]
的时间范围呢?这个 [1m]
不是用来计算的,irate 在计算的时候会最多向前在 [1m]
范围内找点,如果超过 [1m]
没有找到数据点,这个点的计算就放弃了。
# 计算过去5分钟内的http_requests_total时间序列值的瞬时增长速率
irate(http_requests_total[5m])
2
# 2.3 increase
increase(vector[duration])
:计算时间序列值的增量 通过计算区间向量中的第一个和最后一个样本并返回其增长量。
# 计算过去1小时内http_requests_total时间序列值的增量。
increase(http_requests_total[1h])
2
# 2.4 更多
更多函数参考官方文档 (opens new window)
# 四、高级查询
# 4.1 offset
offset
关键字用于对时间序列数据进行时间偏移。它可以用来查看相对于当前时间之前或之后的数据点。
# 所查询指标数据点向前偏移1小时
prometheus_http_requests_total{code="200"}[5m] offset 1h
2
# 4.2 label_replace
relabel_replace
函数用于重新标记(relabel)时间序列的标签(labels)并替换它们的值。该函数通常用于对时间序列进行标准化、重命名或筛选。语法格式: label_replace(v instant-vector, dst_label string, replacement string, src_label string, regex string)
- 重命名标签值
label_replace(prometheus_http_requests_total,"code","500","code","503") // code 为503的标签的值 替换成500
- 移除标签
只是移除标签,该条数据还是存在
label_replace(prometheus_http_requests_total, "", "", "code", "503") // 将 code 标签值为 "500" 的时间序列的 status 标签移除
# 4.3 聚合查询
- sum 对时间序列数据进行求和
sum(prometheus_http_requests_total) // 对所有的 prometheus_http_requests_total 时间序列进行求和
- avg 计算时间序列数据的平均值
avg(temperature) // 计算 temperature 时间序列的平均值
- min 获取时间序列数据的最小值
- max 获取时间序列数据的最大值
- topk 获取时间序列数据中的前K个最大值
topk(5, cpu_usage) // 获取 cpu_usage 时间序列中的前5个最大值
bottomk 获取时间序列数据中的前K个最小值
count 计算时间序列数据的数量
# 4.4 指标向量匹配
# 4.4.1 一对一匹配
一对一匹配(One-to-One Matching)是指通过相同的标签匹配条件,在两个时间序列之间建立一对一的映射关系。这种匹配模式常用于对比或计算两个时间序列之间的差异或相关性。
如果操作符表达式两边标签不一致,,可以使用 on(label list)
或者 ignoring(label list)
来修改便签的匹配行为。使用 ignoreing
可以在匹配时忽略某些便签。而 on
则用于将匹配行为限定在某些便签之内。
- 标签一致
伪代码标签
metric1{team="tfgol",app="wms"} 12
metric2{team="tfgol",app="wms"} 10
2
匹配结果
# {team="tfgol",app="wms"} 结果为2
metric1{team="tfgol",app="wms"} - metric2{team="tfgol",app="wms"}
2
上面伪代码中 metric1
和 metric2
,它们具有相同的标签匹配条件(例如 team="tfgol",app="wms")。通过执行该表达式,Prometheus将找到具有相同标签值的匹配时间序列,并计算它们之间的差异(metric1
减去 metric2
)。
- 标签不一致
伪代码标签
metric1{team="tfgol",app="wms"} 12
metric2{team="tfgol",app="pos"} 10
2
匹配结果
# 匹配结果为空
metric1{team="tfgol",app="wms"} - metric2{team="tfgol",app="wms"}
2
当我们执行查询语句 metric1 - metric2
的时候,对于向量左边的每一个元素,操作符都会尝试在右边里面找到一个匹配的元素,匹配是通过比较所有的标签来完成的,没有匹配的元素会被丢弃,因此我们匹配不到,所以就丢弃了,针对上面情况我们可以使用on
或者 ignoring
修饰符来指定用于匹配的标签进行计算。
- on和ignoring
伪代码标签
metric1{team="tfgol",app="wms"} 12
metric2{team="tfgol",app="pos"} 10
2
匹配结果
# on 指定标签匹配 其他则忽略 下面指定team标签
metric1{team="tfgol",app="wms"} - on(team) metric2{team="tfgol",app="wms"}
# ignoring 忽略标签匹配 下面忽略app标签
metric1{team="tfgol",app="wms"} - ignoring(app) metric2{team="tfgol",app="wms"}
2
3
4
# 4.4.2 多对一和一对多
多对一和一对多两种匹配模式指的是一
侧的每一个向量元素可以与多
侧的多个元素匹配的情况,在这种情况下,必须使用 group 修饰符:group_left
或者 group_right
来确定哪一个向量具有更高的基数(充当多
的角色)。多对一和一对多两种模式一定是出现在操作符两侧表达式返回的向量标签不一致的情况,因此同样需要使用 ignoring
和 on
修饰符来排除或者限定匹配的标签列表。
- group_left
group_left 以表达式左边为基数,匹配结果中,会显示左边表达式向量的所有标签
伪代码
metric1{team="tfgol",app="wms",area="sh"} 12
metric2{team="tfgol",app="pos"} 10
2
匹配结果
metric1{team="tfgol"} - on(team,app) group_left metric2{team="tfgol"}
# 匹配结果
metric1{team="tfgol",app="wms",area="sh"} 2
2
3
4