PromQL介绍

Prometheus提供了一种功能强大的表达式语言PromQL(Prometheus Query Language)

Prometheus允许用户实时选择和汇聚时间序列数据,是Prometheus自己开发的数据查询语言,使用这个查询语言能够进行各种聚合、分析和计算,使管理员能够根据指标更好地了解系统性能

PromQL 的执行可以通过两种方式来触发:

  • 在 Prometheus 服务器中,记录规则和警报规则会定期运行,并执行查询操作来计算规则结果(例如触发报警,该执行在 Prometheus 服务内部进行,并在配置规则时自动发生
  • 外部用户和 UI 界面可以使用 Prometheus 服务提供的 HTTP API 来执行 PromQL 查询,这就是仪表盘软件(例如 GrafanaPromLens 以及 Prometheus 内置 Web UI)访问 PromQL 的方式

PromQL查询语言 - 图1

时序数据库

Prometheus是一款时序数据库TSDB,它结合生态系统内的其他组件例如Pushgateway、Alertmanager等可构成一个完整的IT监控系统

时序数据库的特点如下:

  • 数据写入特点——写入平稳、持续、高并发高吞吐;写多读少,在写操作上数据上能达到95%以上;无更新时写入最近生成的数据
  • 数据查询特点——按时间范围读取一段时间的数据;对最近生成的数据读取概率高,对历史数据查询概率低;按照数据点的不同密集度实现多精度查询
  • 数据存储特点——数据存储量比较大;具有时效性,数据通常会有一个保存周期,多精度数据存储

对时序数据库的基本要求如下:

  • 能够支持高并发、高吞吐的写入
  • 交互级的聚合查询,能够达到低查询延迟
  • 依据场景设计可以支持海量数据存储
  • 在线应用服务场景中,需要高可用架构支持
  • 针对写入和存储量的要求,应用环境底层需要分布式架构支持

时序数据

PromQL查询语言 - 图2

时间序列数据:按照时间顺序记录系统、设备状态变化的数据,每个数据称为一个样本

数据采集以特定的时间周期进行,随着时间的流逝,将这些样本数据记录下来,生成一个离散的样本数据序列

把序列称作为向量,而将多个序列放在同一个坐标系内(以时间为横轴,以序列为纵轴,形成一个有数据点组成的矩阵)

即时向量:特定或全部的时间序列上的集合,具有相同时间戳的一组样本称之为即时向量

范围向量:特定或全部的时间序列上的集合,在指定的同一时间范围内的所有样本值称之为范围向量

时间序列选择器

即时向量选择器

即时向量选择器由两部分组成

  • 指标名称:用于限定特定指标下的时间序列,即负责过滤指标,可选
  • 匹配器:或称为标签选择器,用于过滤时间序列上的标签,定义在{}中
  • 使用举例:

    • prometheus_http_requests_total(仅给定指标名称)
    • {job="node_exporter"}(仅给定匹配器)
    • up{job="node_exporter"}(指标名称和匹配器的组合)

匹配器用于定义标签过滤的条件,目前支持如下四种

  • =:相等匹配模式,用来指定返回的时间序列与指定的标签完全匹配
  • !=:反向匹配模式,即不等于模式
  • =~:正则表达式匹配模式
  • !~:反向正则表达式

范围向量选择器

同即时向量选择器唯一不同的地方在于,范围向量选择器需要在表达式后紧跟一个方括号[]来表达需要,在时序上返回的样本所处的时间范围

时间范围:以当前时间为基准点,指向过去一个特定的时间长度,例如[5m],便是指过去5分钟之内

可用的时间单位有:ms、s、m、h、d、w、y

必须使用整数时间,例如[1h30m],但不允许使用[1.5h]

PS:

需要注意的是,范围向量选择器返回的是一定时间范围内的数据样本,虽然不同时间序列的数据抓点时间点相同,但是多个Target上的数据抓取需要分散在抓取时间点的周围,他们的时间戳并不会严格对齐

Prometheus抓取的监控项的数目是十分庞大的,如果时间戳精准的话,CPU会承受不住,这种不严格的抓取目的是为了均衡Prometheus的负载

偏移量修改器

默认情况下,即时向量选择器和范围向量选择器都以当前时间为基准,而偏移量修改器能够修改该基准

例如:

  • up{job="node_exporter"}[5m]表示的是获取过去5分钟的即时样本

  • up{job="node_exporter"}[5m] offset 1d表示的是获取过去1天的即时样本

PromQL语言

工作中常用,面试少问

PromQL操作符

PromQL支持的所有数学运算符有:+ - * / % ^

PromQL支持的所有布尔运算符有:== != > < >= <=

使用瞬时向量表达式能够获取到一个包含多个时间序列的集合,我们称为瞬时向量,通过集合运算,可以在两个瞬时向量与瞬时向量之间进行相应的集合操作

PromQL支持的所有布尔运算符有:and (交集) or (并集) unless (差集)

操作符优先级

在PromQL操作符中优先级由高到低依次为

  1. ^
  2. * , / , %
  3. + , -
  4. == , != , <= , < , >= , >
  5. and , unless
  6. or

聚合操作

Prometheus提供了下列内置的聚合操作符,这些操作符作用于瞬时向量,可以将瞬时表达式返回的样本数据进行聚合,形成一个新的时间序列

  • sum (求和)

  • min (最小值)

  • max (最大值)

  • avg (平均值)

  • stddev (标准差)

  • stdvar (标准差异)

  • count (计数)

  • count_values (对value进行计数)

  • bottomk (后n条时序)

  • topk (前n条时序)

  • quantile (分布统计)

  1. # 语法格式
  2. <aggr-op>([parameter,] <vector expression>) [without|by (<label list>)]
  3. # 只有count_values,bottomk,topk,quantile支持参数parameter
  4. # without用于从计算结果中移除列举的标签,而保留其它标签
  5. # by则正好相反,结果向量中只保留列出的标签,其余标签则移除
  6. # 通过without和by可以按照样本的问题对数据进行聚合

PromQL查询语言 - 图3

例如:

  1. sum(prometheus_http_requests_total) without (instance)
  2. # 等价于
  3. sum(prometheus_http_requests_total) by (code,handler,job,method)
  • count计算HTTP请求样本的数量
  1. count(prometheus_http_requests_total)

PromQL查询语言 - 图4

  • sum计算整个应用的HTTP请求总量
  1. sum(prometheus_http_requests_total)

PromQL查询语言 - 图5

  • count_values用于统计时间序列中每一个样本值出现的次数,count_values会为每一个唯一的样本值输出一个时间序列,并且每一个时间序列包含一个额外的标签
  1. count_values("number", prometheus_http_requests_total)

PromQL查询语言 - 图6

  • topk和bottomk则用于对样本值进行排序,返回当前样本值前n位,或者后n位的时间序列

获取HTTP请求数最多的前5位的时序样本数据,可以使用表达式:

  1. topk(5, prometheus_http_requests_total)

PromQL查询语言 - 图7

  • quantile用于计算当前样本数据值的分布情况 quantile(φ, express) 其中0 ≤ φ ≤ 1

当φ为0.5时,即表示找到当前样本数据中的中位数:

  1. quantile(0.5, http_requests_total)

PromQL查询语言 - 图8

向量匹配

one-to-one

一对一向量匹配模式,它从运算符的两侧表达式中获取即时向量,依次比较并找到一对唯一条目进行匹配,如果两个条目具有完全相同的标签和对应的值,则他们匹配

在操作符两边表达式标签不一致的情况下,可以使用on(label list)或者ignoring(label list)来修改便签的匹配行为,使用ignoreing可以在匹配时忽略某些便签,而on则用于将匹配行为限定在某些便签之内

语法格式:

  1. <vector expr> <bin-op> ignoring(<label list>) <vector expr>
  2. <vector expr> <bin-op> on(<label list>) <vector expr>

例如存在样本:

  1. method_code:http_errors:rate5m{method="get", code="500"} 24
  2. method_code:http_errors:rate5m{method="get", code="404"} 30
  3. method_code:http_errors:rate5m{method="put", code="501"} 3
  4. method_code:http_errors:rate5m{method="post", code="500"} 6
  5. method_code:http_errors:rate5m{method="post", code="404"} 21
  6. method:http_requests:rate5m{method="get"} 600
  7. method:http_requests:rate5m{method="del"} 34
  8. method:http_requests:rate5m{method="post"} 120

使用PromQL表达式:

  1. method_code:http_errors:rate5m{code="500"} / ignoring(code) method:http_requests:rate5m

该表达式会返回在过去5分钟内,HTTP请求状态码为500的在所有请求中的比例,如果没有使用 ignoring(code),操作符两边表达式返回的瞬时向量中将找不到任何一个标签完全相同的匹配项

many-to-one和one-to-many

多对一和一对多的匹配模式,可以理解为向量元素中的一个样本数据匹配到了多个样本数据标签,在使用该匹配模式时,需要使用group_left或者group_right修饰符明确指定哪一个向量具有更高的基数,也就是说左或者右决定了哪边的向量具有较高的子集

语法格式:

  1. <vector expr> <bin-op> ignoring(<label list>) group_left(<label list>)
  2. <vector expr>
  3. <vector expr> <bin-op> ignoring(<label list>) group_right(<label list>)
  4. <vector expr>
  5. <vector expr> <bin-op> on(<label list>) group_left(<label list>) <vector
  6. expr>
  7. <vector expr> <bin-op> on(<label list>) group_right(<label list>) <vector
  8. expr>

多对一和一对多两种模式一定是出现在操作符两侧表达式返回的向量标签不一致的情况,因此需要使用 ignoringon修饰符来排除或者限定匹配的标签列表

例如,使用表达式:

  1. method_code:http_errors:rate5m / ignoring(code) group_left method:http_requests:rate5m

该表达式中,左向量 method_code:http_errors:rate5m 包含两个标签method和code;而右向量 method:http_requests:rate5m 中只包含一个标签method,因此匹配时需要使用ignoring限定匹配的标签为code,在限定匹配标签后,右向量中的元素可能匹配到多个左向量中的元素 因此该表达式的匹配模式为多对一,需要使用group修饰符group_left指定左向量具有更好的基数

最终运算结果如下

  1. {method="get", code="500"} 0.04 // 24 / 600
  2. {method="get", code="404"} 0.05 // 30 / 600
  3. {method="post", code="500"} 0.05 // 6 / 120
  4. {method="post", code="404"} 0.175 // 21 / 120

内置函数

计算Counter指标增长率

Counter类型的监控指标其特点是只增不减,在没有发生重置(如服务器重启,应用重启)的情况下其样本值应该是不断增大的,为了能够更直观的表示样本数据的变化剧烈情况,需要计算样本的增长速率

  • increase函数获取区间向量中的第一个和最后一个样本并返回其增长量
  1. increase(node_cpu_seconds_total[2m])/120

这里通过node_cpu[2m]获取时间序列最近两分钟的所有样本,increase计算出最近两分钟的增长量, 最后除以时间120秒得到node_cpu样本在最近两分钟的平均增长率

  • rate函数直接计算区间向量在时间窗口内的平均增长速率
  1. rate(node_cpu_seconds_total[2m])

获得的结果和上面increase表达式的结果一样

  • irate函数计算了区间向量在事件窗口内的瞬时增长速率,通过区间向量中最后两个数据来计算区间向量的增长速率,这种方式可以避免在时间窗口内的“长尾问题”
  1. irate(node_cpu_seconds_total[2m])

irate函数相比于rate函数提供了更高的灵敏度,不过当需要分析长期趋势或者在告警规则中,irate的这种灵敏度反而容易造成干扰,因此在长期趋势分析或者告警中更推荐使用rate函数

预测Gauge指标变化趋势

predict_linear函数可以预测时间序列v在t秒后的值,它基于简单线性回归的方式,对时间窗口内的样本数据进行统计,从而可以对时间序列的变化趋势做出预测

基于2小时的样本数据,来预测主机可用磁盘空间是否会在4个小时内被占满:

  1. predict_linear(node_filesystem_free_bytes[2h], 4 * 3600) < 0

统计Histogram指标的分位数

histogram_quantile(φ float, b instant-vector)函数来计算Histogram的分位数,其中 φ(0<φ<1)表示需要计算的分位数,如果需要计算中位数φ取值为0.5

计算中位数:

  1. histogram_quantile(0.5, prometheus_http_request_duration_seconds_bucket)