深入理解 PromQL

972次阅读  |  发布于1年以前

Prometheus 监控系统是云原生环境下主流的监控系统,在各大厂都有比较广泛的应用。

Prometheus 开创性地提出了 PromQL 查询语法,大大简化了监控面板的配置门槛,使得应用开发者可以自由地配置、组合监控面板。

PromQL 功能非常强大,大部分应用开发者只需要了解最简单的函数(如 rate、delta、histogram_quantile)就可以实现绝大部分需求。另一方面,这也导致很多人对 PromQL 并没有很深入的理解,无法掌握一些高级查询功能,遇到一些报错的时候不明所以。

比如:

1、案例1:offset位置不对报错,必须在 selector 或者 subquery 后面

2、案例2:ranges只能接在 vector selectors 后面

本文试图阐述 PromQL 的组成部分,帮助大家深入的理解 PromQL 语句的含义,以及能够根据所想写出合适的 PromQL 语句。

PromQL 解析

四则混合运算,可以拆分成 数字、操作符和括号,掌握了运算规则,再长的算式都变得很好理解。PromQL 也是如此。

Prometheus的指标(Metric)包括 Counter、Gauge、Histogram、Summary 四种基本类型,部分 PromQL的函数确实也有要求指定的类型,但这里的细节不在本文的讨论范围内。

PromQL 主要包含以下几个组成部分(下列组成部分的划分是我个人根据自身的经验和理解做出的,如有不同意见欢迎探讨)

一个 PromQL 表达式,即是由上述各部分组成的,理解了每个部分的含义,复杂的 PromQL 就很好理解了。更进一步也可以按自己的心意写出复杂的 PromQL 语句。

本文并不致力于详尽的讲解每一个组成部分,只想澄清最关键的一些概念。一些组成部分的细节(如具体的函数)可以去查阅官方文档。以上各个部分其实在官方文档上都有提及,但是散落在不同的页面,不是很好理解其中的关系。

Vectors

Vector是什么

Vector直接翻译是向量的意思,PromQL 中的 Vector 也可以理解为向量。

以一个时间序列(TimeSeries)为例

counter{a="b", c="d"}

等价于

{a="b", c="d", __name__="counter"}

Instant Vector && Range Vector

刚刚讲的 dimension 可以理解为 向量的方向,向量还有一个元素就是向量值,在 Prometheus中,向量值都是浮点型的数字。

在一个时刻有一个向量值的,就叫做 instant vector

在一个时刻,不仅包含当前时刻的值,还包含前向一段时间范围的 向量值(确切的说是时刻=>值的键值对)的,就叫做 range vector

对于 instant vector,加上时间维度后,可以很容易地画出图像,横坐标是时间戳,纵坐标是向量值。

对于 range vector,每一个时刻,都包含一组键值对信息,例如

以 example_metric{job="C"} 为例,可以看到 1@1(value@timestamp) 这个点,出现在了上述所有时刻的 ran ge里。

在做运算的时候,可能会用到部分或全部这些信息。例如

Vector扩展——相关注意点

上述两点,是想说明 Prometheus 适合用于趋势类监控,并不能做到十分精确。在某一项指标的具体一小段时间,尤其是 irate 这样的函数结果并不能精确的反应真实情况。不要用 Prometheus 做时间灵敏度、精确度高的监控手段。

另外,基于采样的监控,采样间隔期间出现的瞬时峰值也是无法监控到的。

Selectors

Selector是什么

Selector——选择器——是一个基于标签匹配来获取符合条件的timeseries的PromQL对象

Selector 可以定义一组label及其对应的匹配规则,一共有四种:

标签的匹配和存储是基于倒排索引来实现的。

指标名称也是标签,是一个特殊的标签__name__

Prometheus的正则匹配是对 label 值的完全匹配,不支持部分匹配。正则匹配支持的规则也有一定限制(与go官方的regex库支持范围一致),如无法支持“look behind”等。

Selector和Vector的关系

相同点

不同点

SubQuery

SubQuery 本质上是 range vector,是 instant vector 附加上 [range:resolution] 之后得到的。

在 PromQL 支持的函数中,如果涉及到 instant vector 和 range vector 之间的转换,几乎部分都是从 range vector 转换到 instant vector。这也不难理解,因为 range vector 所包含的信息更多,这些函数,本质上都是接受较多信息作为入参,计算出一维的结果进行返回。

例如 irate 函数,可以计算一个 counter 指标的变化率

irate(counter{a="b", c="d"}[5m])

如果我想计算近5分钟变化率的最大值,该怎么办?这里就用到了 SubQuery

max_over_time(irate(counter{a="b", c="d"}[5m])[5m:1m])

irate 返回的结果是一个 instant vector,拼接上 [range:resolution] 之后,就成为了一个 subquery,也就是一个 range vector,可以被用于 max_over_time 函数的入参。

为什么resolution是必须的

根据官方文档, SubQuery 的标准定义为

Syntax: <instant_query> '[' <range> ':' [<resolution>] ']' [ @ <float_literal> ] [ offset <duration> ]

<resolution> is optional. Default is the global evaluation interval.

官方文档这里描述的有点坑,比如既然 resolution 是可选的,为什么还会报下面的错误?

答案是 虽然 resolution 是可选的,但是冒号不能丢,可以写成这样:

max_over_time(irate(vector(5)[5m:1m])[5m:])

此时,默认的 resolution,等于 prometheus 配置中的 global evaluation interval,也就是来自官网配置文档中的下面这项配置:

为什么 SubQuery 中 resolution 是必要的?按照我的理解,这是因为很多函数计算严格依赖 resolution。例如

sum_over_time(vector(5)[5m:1m]) ==> 25
sum_over_time(vector(5)[5m:2m]) ==> 10

resolution 在上面的表达式里,直接决定了range内有几个点参与计算。

与之对比,在 selector 层面,range query selector 中的时间范围可以不加 resolution。这是因为对于一个具体的 timeseries,其自身天然包含 resolution信息(等于指标采集时的间隔)。但是对于SubQuery,必须需要一个resolution,来确定range内的点的粒度和个数。

各组件关系转换图

总结

PromQL 本质上是针对一系列 vector 的操作:selector 是 TimeSeries 转换为 Vector 的桥梁,查出来的结果是 full-dimensional vector。然后,再通过一系列函数、操作符,针对 vector 的粒度,来进行运算。

本文试图将 PromQL 解析为基本的组成部分,并对其中的关键点、易混淆的概念进行了解析。略去的 Operators、Functions等,读者可以自行去官网查看详细的使用说明。相信有了本文作为基础,理解官方文档也会更加快速、更加透彻。

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8