「译文」剖析 PromQL 查询

本文最后更新于:2024年7月24日 晚上

👉️URL: https://promlabs.com/blog/2020/06/18/the-anatomy-of-a-promql-query

📝Description:

PromLabs - Products and services around the Prometheus monitoring system to make Prometheus work for you

让我们来看看 PromQL 查询的解剖结构。PromQL 有运算符、函数、选择器等等,但在这篇文章中我们不要被这些细节所困扰。相反,让我们看看查询的整体性质。PromQL 查询是如何结构化和类型化的,以及它们是如何随着时间的推移被评估的?

这篇博文假设你对 PromQL 查询和 Prometheus 数据模型 有一个非常粗略的概念,但除此之外没有其他想法。让我们开始吧!

PromQL 是一种嵌套的函数式语言

与 SQL 或其他一些更倾向于命令式的查询语言(SELECT * FROM ...)不同,PromQL 是一种嵌套的函数式语言。这意味着你把你要找的数据描述成一组嵌套的表达式,每个表达式都评估(没有副作用)到一个中间值。每个中间值被用作其周围表达式的参数或操作数,而查询的最外层表达式代表最终的返回值,你可以在表格、图表或类似的使用情况下看到。

一个查询的例子可以是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 查询的根,最后的结果,近似于一个量化。
histogram_quantile(
# histogram_quantile() 的第一个参数,即目标量化指标。
0.9,
# histogram_quantile() 的第二个参数,是一个聚合的直方图。
sum by(le, method, path) (
# sum() 的参数,即过去 5m 直方图每秒钟的增幅。
rate(
# rate() 的参数,过去 5m 的原始直方图系列。
demo_api_request_duration_seconds_bucket{job="demo"}[5m]
)
)
)

PromQL 表达式不仅仅是整个查询,而是查询的任何嵌套部分(比如上面的 rate(..) 部分),你可以把它作为一个查询本身来运行。在上面的例子中,每行注释代表一个表达式。

当你在 PromLabs 的 PromLens query visualizer 中分析同一个查询时,这种包含子表达式的嵌套结构会变得特别清晰。

PromLens 中的可视化查询结构

在 PromLens 中,你可以点击每个树节点来分别评估其子表达式,以了解你在嵌套表达式树的每个部分中处理的数据。

表达式是可以类型化的,但也许不像你想的那样

普罗米修斯中出现了两个 " 类型 " 的概念。

  • 指标的类型,由一个被刮削的目标报告:计数器、量表、直方图、总计或无类型的 (counter, gauge, histogram, summary, or untyped.)。
  • PromQL 表达式的 类型:字符串、标量、即时向量或范围向量 (string, scalar, instant vector, or range vector.)。

PromQL 完全忽略了 指标类型 ,只关注 表达式类型 。每个表达式都有一个类型,每个函数、运算符或其他类型的操作都要求其参数是某种表达式类型。例如,rate() 函数要求它的参数是一个范围向量,但是 rate() 本身的值是一个即时向量。因此反过来,rate()的结果只能在期望即时向量的地方使用。

PromQL 中可能的表达式类型是:

  • **string:** 一个字符串,如" 我是一个字符串!"。这些只作为某些函数(如label_join())的参数出现,除此之外,在 PromQL 中不怎么使用。

  • **scalar:** 一个单一的数值,如 1.234,没有标签维度。你会看到这些作为函数的数字参数,如histogram_quantile(0.9, ...)topk(3, ...),也可以在算术运算中看到。

  • 即时向量 :一组标记的时间序列,每个序列有 一个样本,都在同一个时间戳。瞬时向量可以由 TSDB 时间序列选择器直接产生,如node_cpu_seconds_total,也可以由任何函数或其他转化器返回。

    1
    2
    3
    node_cpu_seconds_total{cpu="0", mode="idle"}   → 19165078.75 @ timestamp_1
    node_cpu_seconds_total{cpu="0", mode="system"} → 381598.72 @ timestamp_1
    node_cpu_seconds_total{cpu="0", mode="user"} → 23211630.97 @ timestamp_1
  • 范围向量 :一组标记的时间序列,每个序列都有一个 时间范围的样本 。在 PromQL 中只有两种方法产生范围向量:在查询中使用字面范围向量选择器(如node_cpu_seconds_total[5m]),或使用子查询(如<expression>[5m:10s])。范围向量对于你想在指定的时间窗口内汇总一个系列的行为很有用,就像你用rate(node_cpu_seconds_total[5m]) 来计算过去 5 分钟内 node_cpu_seconds_total 指标的平均每秒增长率。

    1
    2
    3
    node_cpu_seconds_total{cpu="0", mode="idle"}   → 19165078.75 @ timestamp_1,  19165136.3 @ timestamp_2, 19165167.72 @ timestamp_3
    node_cpu_seconds_total{cpu="0", mode="system"} → 381598.72 @ timestamp_1, 381599.98 @ timestamp_2, 381600.58 @ timestamp_3
    node_cpu_seconds_total{cpu="0", mode="user"} → 23211630.97 @ timestamp_1, 23211711.34 @ timestamp_2, 23211748.64 @ timestamp_3

** 但是指标的类型呢?** 如果你已经使用过 PromQL,你可能知道某些函数只对特定类型的指标起作用例如,histogram_quantile()函数只对直方图指标起作用,rate()只对计数器指标起作用,deriv()只对量表起作用。但是 PromQL 实际上并不检查你是否传入了正确的度量类型 - 这些函数通常会很高兴地运行,并为错误的输入度量类型返回一些无意义的东西,这取决于用户是否传入遵守某些假设的时间序列(比如在直方图的情况下有一个敏感的le 标签,或者在计数器的情况下单调地增加)。然而,在未来,我们可能会看到诸如 PromLens 这样的用户界面在你向函数传递不兼容的度量类型时尝试警告你。

时间是怎么来的?范围和即时查询

你可能已经注意到,PromQL 查询中对时间的引用只有相对引用(如[5m],回看 5 分钟)。那么,你如何指定一个绝对的图形时间范围,或一个在表中显示查询结果的时间戳?在 PromQL 中,这种时间参数与表达式分开发送至 Prometheus query API,确切的时间参数取决于你所发送的查询类型。普罗米修斯知道两种类型的 PromQL 查询:即时查询和范围查询。

即时查询

即时查询 用于 表式视图,你想在一个时间点上显示 PromQL 查询的结果。

一个即时查询有以下参数:

  • PromQL 表达式。
  • 一个评估的时间戳。

表达式在评估的时间戳处被评估,查询中的任何数据选择器被允许选择从该时间戳延伸到过去的数据(foo[1h]选择 foo 系列的最后一小时的数据),但绝不会延伸到未来(foo[-1h]是无效的 PromQL)。访问过去的数据窗口对于计算一段时期内的比率或平均数等总量是非常有用的。

一个即时查询可以返回任何有效的 PromQL 表达式类型(字符串、标量、即时和范围向量)。

即时查询示例:

让我们看一下一个即时查询的例子,看看它的评估是如何进行的。想象一下,在一个给定的时间戳上评估表达式 http_requests_totalhttp_requests_total 是一个即时向量选择器,它选择任何具有 http_requests_total 指标的时间序列的最新样本。更具体地说," 最新 " 意味着 “最多 5 分钟,并且不陈旧”,相对于评估时间戳而言。所以这个选择器只会产生一个系列的结果,这个系列在评估时间戳之前最多只有 5 分钟的样本,而且评估时间戳之前的最后一个样本不是陈旧的标记(在 Prometheus TSDB 中明确标记一个系列在某个时间终止的方式)。

如果我们在一个有最近样本的时间戳上运行这个查询,结果将包含 两个系列,每个系列有一个样本

非陈旧性即时查询图

请注意,每个返回样本的输出时间戳不再是原始样本的时间戳,而是被设置为评估时间戳。

想象一下,在时间戳之前有一个 >5m 的数据间隙的情况下执行这个相同的查询。

陈旧的即时查询图

在这种情况下,查询将返回一个 的结果,因为所有匹配的样本都太老了,不能被包括在内。

范围查询

范围查询 主要用于,你想在一个给定的时间范围内显示一个 PromQL 表达。一个范围查询的工作原理与许多完全独立的即时查询完全一样,这些查询在给定的时间范围内的后续时间步骤中被评估。当然,这在后台是高度优化的,在这种情况下,Prometheus 实际上并不运行许多独立的即时查询。

一个范围查询有以下参数。

  • PromQL 表达式。
  • 开始时间
  • 结束时间
  • A resolution step. (分辨率步长)

在开始时间和结束时间之间的每个分辨率上评估表达式后,单独评估的时间片被拼接成一个范围向量。范围查询允许传入即时向量类型或标量类型的表达式,但总是返回一个范围向量(标量或即时向量在一个时间范围内被评估的结果)。

范围查询示例:

如果我们把上面的例子表达式作为一个范围查询来评估,它看起来会是这样的(例子查询的 resolution step 是 2.5 分钟)。

Range query diagram

请注意每个评估步骤的行为完全像一个独立的即时查询,而且每个独立的即时查询对查询的整体范围没有概念。还要注意一些后续的 resolution step 最终是如何选择相同的底层原始样本作为其输出样本值的,而这个原始样本恰好还是这两个步骤的最新样本(而且不超过 5 分钟)。

在这种情况下,最终的结果将是一个范围向量,其中有两个选定的系列在一定时间范围内的样本,但也会在一些时间步骤中包含系列数据的间隙。

综上所述

希望这篇博文能让你更好地了解 PromQL 查询的整体结构、正在进行的类型检查(或缺乏检查),以及随着时间推移对查询的评估。如果你有任何问题或意见,请在下面评论,并继续关注更多关于 PromQL 的文章!