背景
PromQL是Prometheus项目针对时序场景提供的一种查询语言,而PromQL的设计与常规意义上的SQL语句区别较大,其执行引擎的计算逻辑同样也大相径庭。在日常的客服解答中,发现较多客户对PromQL的执行原理有误解,故整理此篇文章对PromQL的原理与执行逻辑进行详细介绍。
对时序的认识
时序场景下的观测对象是 “Metric”,例如这项表示“进程的常驻内存使用量”的指标Metric process_resident_memory_bytes
。首先介绍“时间线”的概念,Prometheus通过多个Label标签值来区分不同的时间线,从下图我们可以看到该指标存在三条时间线,分别是:
时间线1: instance="demo.promlabs.com:10000", job="demo"
时间线2: instance="demo.promlabs.com:10001", job="demo"
时间线3: instance="demo.promlabs.com:10002", job="demo"
上述三条时间线存在两个Label标签,分别为表示“进程”的job
和表示实例instance
。从现实意义上看process_resident_memory_bytes
这项指标,绿色折线即表示进程名为”demo“且实例名为“demo.promlabs.com:10000”的一个进程的常驻内存占用情况。Metric时序场景中,不同的Label值列表则表示一条新的时间线,每个Label都由Key/Value值组成,例如 job="demo" 这个Label中的Key值为“job”、Value值为“demo”。下面给出了Label在源码中的数据结构定义,
type Label struct {
Name, Value string
}
接着介绍Promethus时序模型中的“采样点”,每个采样点由Label列表、时间戳、数值组成,下面同样给出了数据结构的定义,
type Point struct {
T int64 // 时间戳, ms精度
V float64 // 数值
}
type Sample struct {
Point
lables []Label // Label列表
}
数据采集
Prometheus通过http访问目标进程实例的/metrics
接口拉取指标数据,而指标拉取的时间间隔(例如,5秒)可通过配置文件进行自定义配置。我们假设拉取到的指标仍是process_resident_memory_bytes
、时间线仍是前面列举的三条时间线、每隔5秒拉取一次指标,那么在查询近10分钟的数据时,理论上会有 600/5*3+3 (即 363) 个数据点。以 00:00 ~ 10:00为例,指标采样点的时间戳分别为00:05/00:10/00:15.........09:50/09:55/10:00,示例图可见下图:
上图给出了指标采样点的示例图,而在实际的生产环境中,采样的时间戳并不会如此规整。例如发起采样的时间点为00:03,那么后续的时间点则为00:08/00:13/00:18......;此外,受并发、网络延时等影响,采集间隔也可能不会完全严格遵守5秒间隔的约束。
PromQL Engine执行原理
前文介绍了Prometheus中的时序模型、时间线、采样点等概念,接下来将介绍Prometheus计算引擎执行时序查询的的原理。Prometheus为时序查询提供了时序查询设计了一种查询语言,即PromQL(Prometheus query language)。
查询分类
在实际的生产环境中,PromQL的使用主要可分为两种:纯查询型、计算型。
- 纯查询型的PromQL
此类型的PromQL不包含任何计算步骤,仅从Metric的原始指标数据中筛选出符合条件的所有时间线数据。例如,Metric{}
、Metric{job="demo"}
。
- 计算型的PromQL
此类型的PromQL包含聚合算子、一/二元计算表达式、函数等所有涉及了计算过程的查询语句。例如,max(Metric), irate(Metric[1m]), Metric + 1
。
另外,Prometheus为时序查询提供了/query
和query_range
两个查询接口:
- /query接口
此接口仅会返回当前时间戳下的各时间线的最新值。
- /query_range接口
此接口会返回 start 到 end 这个时间区间内各时间线的多个值,后续都以 /query_range 接口为例进行解析。
计算过程
PromQL引擎的执行逻辑与常见的SQL执行方式差异较大,此处将分别介绍“纯查询型”和“计算型”两种类型PromQL的计算过程。
step参数
这里首先介绍执行PromQL计算时一项必须的参数step,理解了该参数的含义才能更好的理解PromQL的计算原理。
该参数表示执行计算的间隔区间,即表示从startTime开始每间隔step的长度都执行一轮计算。通过下图来展示step参数的含义:从00:00时间开始,每间隔40s执行一轮计算,总计将执行16轮计算。
PromQL的所有查询计算都遵循上述这个”每间隔step执行一轮计算“流程,而几类PromQL的区别主要就体现在”每轮计算“中,即”每轮计算“的执行逻辑差异较大。
纯查询型
/query_range的查询主要有以下四个参数:
query, 表示查询语句, 例如 up{instance="1"}
start, 表示查询区间的起始时间戳, 以秒为单位
end, 表示查询区间的截止时间戳,以秒为单位
step, 表示查询时的位移, 以秒为单位
执行查询时首先会根据start和end两个参数拉取到时间范围内的所有数据点,此处以数据采集一节中的数据为案例。
假设 start/end/step三个参数分别为“0, 600, 15”,即首先会将363个数据点都加载到内存中,其次进入到 “选点” 过程。此处的”选点“过程是Prometheus中一种特殊逻辑,流程如下:
- 从start时间戳开始,每间隔 step 的时长会选取一个点值,直至end时间戳结束。以上述案例为例,每条时间线都会进行 600/15+1(即,41)次数据点选取。
- 每次选取数据点的时候,会往前回溯n分钟(默认5分钟)并找到离当前时间戳最近的作为当前时间戳的值。用下图解释这种回溯逻辑,例如原始数据中在 09:28 时刻并不存在数据点,此时会选择 09:25 时刻作为当前时间戳的数据。
在此类型的PromQL查询中,每间隔step执行的”每轮计算“实际仅为数据选取,并未涉及到任何计算过程。针对每条时间线都会进行上述的”选点“处理,那么最后的结果会有41x3(即,123)个数据点。这样就完成了”纯查询型“PromQL的执行流程,最后将结果展示在页面之上。
计算型
计算型的PromQL存在两种计算模式,这里我将它们简称为”纵向“和”横向“计算。例如,max(metric)属于“纵向计算”、max_over_time(metric[30s])属于“横向计算”。下面分别以这两个PromQL来介绍两种截然不同的执行逻辑。
- 纵向计算 -- max(metric)
在每间隔“step”执行一轮计算时,会选取当前时间戳下所有时间线的数据并执行计算,此时的数据选取逻辑与“纯查询型”PromQL一致。例如当前存在n条时间线,那么每次计算都是从n个value中选一个最大的值,且最后的结果仅存在一条折线。如果仍然以前述的数据为例子,123个数据点经计算后的结果仅存在41个数据点。
实际上,这种纵向计算模式对应了Prometheus中的 InstantVectorSelector。
- 横向计算 -- max_over_time(metric[30s])
首先同样是每间隔“step”会执行一次计算,但是计算时则是单独会针对每条时间线进行一次计算。在执行“横向”计算时,那么自然需要“横向”选取数据点。针对此类型的query,需要一个额外的参数“range”,该参数表示横向选取的时间范围。以下面的query为例,
query, 表示查询语句, 例如 max_over_time(metric[30s]), 在这个query中还存在range参数, 即 ”30s“
start, 表示查询区间的起始时间戳, 以秒为单位
end, 表示查询区间的截止时间戳,以秒为单位
step, 表示查询时的位移, 以秒为单位
假设 start、end、step参数分别为”0、600、1m“
上述例子表示每间隔1分钟执行一轮计算,每轮计算都会针对每条时间线进行一次max的计算,并且此时的数据选取逻辑是横向选取”30s“的原始数据。用下面的图进行解释此轮计算流程:在执行本轮计算时,需要对三条时间线都执行一次计算且得到三个数值。假设当前计算时间点为”09:00“,需要用每条时间线在"08:30 ~ 09:00"范围所有的指标值计算一个max值,并用该值作为此时间线在”09:00“时间点的结果值。
以此数据集与PromQL为例,最后的结果会存在三条时间线、123个数据点。
实际上,这种纵向计算模式对应了Prometheus中的 RangeVectorSelector。
使用案例
此小结列举了几个PromQL的使用案例,同样以 instance 和 job 这两个Label为例进行分析。
- 假设当前Metric表示请求的error数量
metric{instance="abc"}:instance为"abc"下所有job的error数量详情
sum(metric):求error数量的总和
sum(metirc) by (instance):对instance进行group by操作并求各类instance下的error数量和
delta(metric[2m]):每种instance和job下,计算2分钟时间区间的error数变化
rate(metric[2m]):每种instance和job下,计算2分钟时间区间的error增长率
- 假设当前Metric表示cpu使用率
max_over_time(metric[2m]):每种instance和job下,计算2分钟时间区间内最大值
avg_over_time(metric[2m]):每种instance和job下,计算2分钟时间区间内平均使用率
stddev_over_time(metric[5m]):每种instance和job下,计算5分钟时间区间内的标准差
总结
本文首先介绍时序场景下的几个基础概念,然后着重对PromQL的执行过程与原理进行了详细介绍,最后列举了多个PromQL的使用案例并进行分析。相信在阅读完上文后,大家对PromQL的执行原理会有一个比较清晰的认识。