Prometheus的PromQL实现分析
文章目录
PromQL
PromQL 是 Prom 中的查询语言,提供了简洁的、贴近自然语言的语法实现时序数据的分析计算。
表达式(Expression)是其中承载数据计算逻辑的部分,对表达式的准确理解有助于充分利用 promql 提供的计算和分析能力,本节先结合一个相对复杂的表达式来介绍 PromQL 的计算过程,然后对部分有代表性的函数实现进行了源码分析。
计算过程
PromQL 表达式输入是一段文本,Prom 会解析这段文本,将它转化为一个结构化的语法树对象,进而实现相应的数据计算逻辑,这里选用一个相对比较复杂的表达式为例:
|
|
上述表达式可以从外往内分解为三层:
- sum(…) by (instance):序列纵向分组合并序列(包含相同的 instance 会分配到一组)
- avg_over_time(…)
- go_goroutines{job=“prometheus”}[5m]
调用 Prom Restful API 查询表达式计算工作流如图 2.1.1 所示,请求数据的时候给出的 step 参数就是这里的 interval,它设定结果中相邻两个点的间隔,对 promql 的每次 evaluator 都是针对某个确定的时间点和 statement 来计算的,得到一个 vector(时间戳相同的向量)。Prom 可以将异构(时间戳不一致)的多维时间序列经过计算转化为同构(时间戳一致)的多维时间序列。
先看 go_goroutines{job=“prometheus”}[5m] 的计算,这是一个某个时间点的 MatrixSelector 对象(图 2.1.2)。
此处 iterator 是序列筛选结果的顺序访问接口,图 2.1.2 中获取某个时间点往前的一段历史数据,这是一个二维矩阵 (matrix),进而由外层函数将这段历史数据汇总成一个 vector(图 2.1.3)。
值得一提的是,很多函数(如 rate)都需要传入 matrix,尽管如此,这些函数的输出依然是针对某个时间点的 vector,它仅仅是在计算某个时间点的 vector 时考察了一部分历史数据而已。
最后来看关键字(keyword)sum 的实现,这里注意 sum 不是函数(Function),图 2.1.4 给出了所有关键字列表。
sum 关键字的完整语法比较复杂,本文中只介绍例子中给出的 sum(…) by (instance)。
至此输出某个时间点的结果向量,整个表达式的计算过程在 Excel 中集中展示如图 2.1.6 所示。
图 2.1.6 sum(avg_over_time(go_goroutines{job=“prometheus”}[5m])) by (instance) 计算过程
PromQL 有三个很简单的原则:
- 任意 PromQL 返回的结果都不是原始数据,即使查询一个具体的 Metric(如 go_goroutines),结果也不是原始数据
- 任意 Metrics 经过 Function 计算后会丢失
__name__
Label - 子序列间具备完全相同的 Label/Value 键值对(可以有不同的
__name__
)才能进行代数运算
特别强调一些,如 2.1.1 所述,PromQL 在计算时使用的等距 interval 时间点,每个 interval 时间点的结果都是利用附近的采样点经过某种形式的估算或近似得到的,所以在 Prom 中提诸如“1:28:07 AM 发生了 113 次某种事件”是不准确的,PromQL 所有计算结果都存在误差。
有意思的是,在 Prom 中对多维时间序列进行代数运算时,不需要严格检查两边的矩阵一致性,因为 PromQL 只会处理相同 Label/Value 的序列之间的代数运算,图 2.1.7 中对两个不相关的 Metric 进行了代数运算,来说明代数运算的基本原理,这在一些以“数据库”为核心的系统中,如 influxdb,涉及跨表运算,无论是表达式复杂度还是计算性能都会有影响。
最后需要特别提的一点是,PromQL 表达式计算的原始数据集是共享内存空间的,但计算的中间结果是不共享内存空间的,所以从优化内存占用的角度来看,应该将常用的表达式持久化成 Metric,减少动态计算过程,让内存使用做到可控,这可以借助 Recording Rules 完成 。
部分函数(Function)实现
Prom 提供了丰富的函数(Function)库来对数据做复杂分析,本节通过介绍几个有代表性的函数实现来介绍其用途,希望能帮助读者准确理解表达式计算结果背后的工程含义。
delta/rate/increase
delta/rate/increase 背后共享了相同的计算逻辑(图 2.2.1),仅仅是参数不同。
来看 extrapolatedRate 实现(图 2.2.2),基于线性外插算法估计了 interval 时间点的采样值增量,Prom 实现中大量使用了线性插值。基本原理很简单,计算 range 范围内采样点头尾斜率,然后线性延伸至实际 interval 时间点。
特别提一下此处的两个参数 isCounter 和 isRate,其中 isCounter=true 说明数据需要保证单调递增,当 Counter 的客户端重启后,数据会归零,出现非单调递增的数据,那么 isCounter 可以控制是否对该数据进行修正;isRate=true 用来对数据做采样范围内的均值,其结果表征当前时间点一秒内的采样值增量(秒级别增量)。
现在回头看图 2.2.1 中 delta/rate/increase 的参数就很明朗了(表 2.2.1)。
可见 delta 在处理数据时,不假设数据单调递增(isCounter=false),适合用来处理 Gauge 数据;而 increase 适合处理 Counter 数据,并获取 range 范围内增量;rate 适合处理 counter 并获取 range 范围内的秒级增量。
XXX_over_time
XXX_over_time 实现 range 范围内数据的横向汇总,即采用 range 范围内的一定量历史数据估算当前时间点的值,其中 XXX 可以是 avg/sum/max/min 等动词,图 2.2.3 中为 XXX_over_time 中的函数 。
由于它们的区别仅仅是对 range 内数据进行横向汇总时的计算方式不同,此处不做一一介绍,只关注其中的 avg_over_time 实现(图 2.2.4)。
avg_over_time 的核心逻辑在 aggrOverTime 中实现,见图 2.2.5。
XXX_over_time 常用来做数据平滑,过滤数据中的异常点,其中 avg_over_time 就是常见的“滑动窗口平均”,在信号处理中为一种低通滤波器实现。
histogram_quantile
histogram_quantile(图 2.2.6)是 Prom 中比较难以理解的函数之一,可以根据 Histogram 估计估算采样数据在某个正态分布分位点的值(实际上估计的是 Upper Bound,即上限)。
估算 quantile 采样值逻辑在 bucketQuantile(图 2.2.7)函数中实现。
总结
Prom 是一种典型的基于 Metric 的监控系统,Metric 是多维时序数据分析在\工程中的一种表现形式。社区中常将 Kubernetes 和 Prometheus 放到一起讨论,它的设计理念和 Kubernetes 也如出一辙:二者都为特定问题提出了标准或协议,为终端用户提供了易用的接口,专注于提供领域价值。
Prom 数据采集主要是通过 pull 模型实现的,主动从客户端拉取数据,减少了监控对象对外部系统的依赖,这种模型下监控对象只需维护少量客户端数据,保持可控、简单的实现,降低了维护复杂客户端逻辑的风险。另外,Prom 为一些临时存在的进程,如批处理任务,提供了 Push Gateway,这些客户端可以将数据 push 到 Push Gateway 中,然后由 Push Gateway 提供 pull 接口将数据暴露给 Prom Server。
相比 Prom,常见的 Metric 监控方案(如 InfluxDB 的 metrics 客户端实现 https://github.com/rcrowley/go-metrics )都是 push 模型,在客户端需要维护采样数据生命周期(如长时间没有存储成功的数据需要丢弃等),还需要避免客户端在数据采集和存储过程中可能出现的资源泄漏。
此外,PromQL 是 Prom 中一个争议和亮点并存的点,它提供了友好的、贴近时序数据语义的语法,对时序数据分析有着丰富的支持,如 Prom 考虑了 Counter 这种单调递增数据由于客户端反复重启导致数据归零的问题,Prom 中很多函数在计算的时候就对这样的数据进行了容错,对数据分析完全透明,极大地提升了易用性;同时 PromQL 提供了 histogram_quantile 根据 Histogram 来估算 quantile 值的计算支持,让 quantile 在 Prom 端计算可以降低客户端带来的额外性能负担。
总之,Prom 数据模型、分析计算接口的设计上都有着良好的一致性和扩展性。基于 pull 的数据采集模型一方面降低了客户端复杂度和对外部系统的依赖,另一方面也让客户端实现自由扩展。反观很多基于 push 模型的监控系统实现,瞬间扩展可能使监控系统服务端出现性能瓶颈,波及整个系统;还有 PromQL 简洁的接口让复杂的时序数据分析变得直观,很多工程上需要处理的数据预处理 Prom 都已经内置了,减少了数据预处理成本。
文章作者 Forz
上次更新 2020-07-17