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 主要包含以下几个组成部分(下列组成部分的划分是我个人根据自身的经验和理解做出的,如有不同意见欢迎探讨)
-
Scalar(标量) -
Vectors -
Instant Vector -
Range Vector -
Selectors -
Instant Query Selector -
Range Query Selector -
Operators -
Arithmetic (算数操作符) -
Trigonometric(三角函数操作符) -
Comparison(比较操作符) -
Logical(逻辑操作符) -
Binary Operators -
Aggregation Operators -
Functions -
SubQuery -
Modifiers
一个 PromQL 表达式,即是由上述各部分组成的,理解了每个部分的含义,复杂的 PromQL 就很好理解了。更进一步也可以按自己的心意写出复杂的 PromQL 语句。
本文并不致力于详尽的讲解每一个组成部分,只想澄清最关键的一些概念。一些组成部分的细节(如具体的函数)可以去查阅官方文档。以上各个部分其实在官方文档上都有提及,但是散落在不同的页面,不是很好理解其中的关系。
Vectors
Vector是什么
Vector直接翻译是向量的意思,PromQL 中的 Vector 也可以理解为向量。
以一个时间序列(TimeSeries)为例
counter{a="b", c="d"}
等价于
{a="b", c="d", __name__="counter"}
-
每一个标签(label)都是一个维度(dimension ),这是一个包含三个维度(dimension)的向量(vector)
-
具有相同 dimension 的 vector 可以认为有相同的秩(线性代数的概念),可以进行匹配等操作。
-
每个 dimension 都有一个标签值,所有unique的标签&&标签值的组合都是一个独立的时间序列(TimeSeries)
-
使用 selector(下面会讲到)查询出的可以认为是全维度(full-dimension)的vector。当执行某些运算或函数的时候,可能出现维度丢失。例如
-
sum by (a) (counter{a="b", c="d"}) 聚合后的 vector 就只剩下了 a 这一个 dimension -
官网未提及 irate(counter{a="b", c="d"}[2m]) 这样的函数,运算完之后,实际上 __name__
已经消失了 -
通过 vector() 函数也可以创建 vector,是一个零维度的 vector
Instant Vector && Range Vector
刚刚讲的 dimension 可以理解为 向量的方向,向量还有一个元素就是向量值,在 Prometheus中,向量值都是浮点型的数字。
在一个时刻有一个向量值的,就叫做 instant vector
在一个时刻,不仅包含当前时刻的值,还包含前向一段时间范围的 向量值(确切的说是时刻=>值
的键值对)的,就叫做 range vector
对于 instant vector,加上时间维度后,可以很容易地画出图像,横坐标是时间戳,纵坐标是向量值。
对于 range vector,每一个时刻,都包含一组键值对信息,例如
以 example_metric{job="C"} 为例,可以看到 1@1(value@timestamp) 这个点,出现在了上述所有时刻的 ran ge里。
在做运算的时候,可能会用到部分或全部这些信息。例如
-
rate、irate 函数,计算变化率,既需要知道 value 的变化,也需要知道 时间的变化。区别是 rate 会取一个 range 的首和尾来计算,irate 会取一个 range 的最新的两个点来计算。这也是为什么 irate 要比 rate 的图像变化波动更大。 -
max_over_time 函数,只需要知道一段 range 里的 value就行了,并不关心时间戳
Vector扩展——相关注意点
-
指标在收集的过程中,可能会丢失某些时刻的sample,一个 range 里也就会丢失部分数据点。丢失数据点后的range在计算过程中,Prometheus会进行一定的兼容处理(如根据一定算法推测缺失的点)。 -
指标在采集的时候,经常是固定时间间隔采集一次(例如15s、30s、1m等),相同 dimension 的指标,在采集时可能会有时间差(前后错开几秒)。Prometheus 内部会有时间戳对齐机制,将相应有偏差的采集点对齐后,再做计算。
上述两点,是想说明 Prometheus 适合用于趋势类监控,并不能做到十分精确。在某一项指标的具体一小段时间,尤其是 irate 这样的函数结果并不能精确的反应真实情况。不要用 Prometheus 做时间灵敏度、精确度高的监控手段。
另外,基于采样的监控,采样间隔期间出现的瞬时峰值也是无法监控到的。
Selectors
Selector是什么
Selector——选择器——是一个基于标签匹配来获取符合条件的timeseries的PromQL对象
Selector 可以定义一组label及其对应的匹配规则,一共有四种:
-
= literally match -
!= literally not match -
=~ regularly match -
!~ regularly not match
标签的匹配和存储是基于倒排索引来实现的。
指标名称也是标签,是一个特殊的标签__name__
。
Prometheus的正则匹配是对 label 值的完全匹配,不支持部分匹配。正则匹配支持的规则也有一定限制(与go官方的regex库支持范围一致),如无法支持“look behind”等。
Selector和Vector的关系
相同点
-
Selector查询出来的其实是TimeSeries,也是包含了这个TimeSeries所有维度的 “full-dimensional” vector -
Selector 和 Vector 都有 Instant 和 Range 两类。Instant Query Selector 可以得到 Instant Vector,Range Query Selector 可以得到 Range Vector
不同点
-
Instant Query Selector 后面加上
[range:resolution]
就变成了 Range Query Selector,其中 冒号和resolution 可以省略Instant Vector 后面加上 [range:resolution] 就变成了 Range Vector(也叫做 SubQuery),其中resolution可以省略,但是冒号不能省略(后文有详细说明)
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等,读者可以自行去官网查看详细的使用说明。相信有了本文作为基础,理解官方文档也会更加快速、更加透彻。
作者介绍
兰孟然,某大厂资深研发工程师,擅长高性能分布式服务端开发,对 prometheus 有深入理解,当前从事 presto、spark 引擎的开发&维护工作。
写文章不易请大家帮忙点击 在看,点赞,分享。
本文分享自微信公众号 - HHFCodeRv(hhfcodearts)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。