Prometheus基础相关--PromQL 基础(4)

知乎 · · 1080 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

见字如面,我是小斐,上文介绍了关于运算、阈值、集合操作等相关操作和说明,本文将继续说明PromQL的基础,上文链接如下:

排序

本节我们将学习如何对查询结果进行排序,或者只选择一组序列中最大或最小的值。

我们可以使用 sort()(升序) 或者 sort_desc()(降序)函数来实现对输出结果进行排序,例如,要显示按值排序的每个路径请求率,从最高到最低,我们可以用下面的语句进行查询:

sort_desc(sum by(path) (rate(demo_api_request_duration_seconds_count{job="demo"}[5m])))

有的时候我们并不是对所有的时间序列感兴趣,只对最大或最小的几个序列感兴趣,我们可以使用 topk()bottomk() 这两个运算符来操作,可以返回 K 个最大或最小的序列,比如只显示每个 path 和 method 的前三的请求率,我们可以使用下面的语句来查询。

topk(3, sum by(path, method) (rate(demo_api_request_duration_seconds_count{job="demo"}[5m])))

练习:

1.构建一个查询以升序的方式显示所有 3 个 demo 服务的磁盘使用情况。

sort(demo_disk_usage_bytes)

2.构建一个查询,按 method、path 和 status 维度显示 3 个最低流量的 demo API 请求比率。

bottomk(3, sum by(method, path, status) (rate(demo_api_request_duration_seconds_count[5m])))

直方图

Prometheus 中的直方图指标允许一个服务记录一系列数值的分布。直方图通常用于跟踪请求的延迟或响应大小等指标值,当然理论上它是可以跟踪任何根据某种分布而产生波动数值的大小。Prometheus 直方图是在客户端对数据进行的采样,它们使用的一些可配置的(例如延迟)bucket 桶对观察到的值进行计数,然后将这些 bucket 作为单独的时间序列暴露出来。

下图是一个非累积直方图的例子:

在 Prometheus 内部,直方图被实现为一组时间序列,每个序列代表指定桶的计数(例如10ms以下的请求数、25ms以下的请求数、50ms以下的请求数等)。 在 Prometheus 中每个 bucket 桶的计数器是累加的,这意味着较大值的桶也包括所有低数值的桶的计数。在作为直方图一部分的每个时间序列上,相应的桶由特殊的 le 标签表示。le 代表的是小于或等于。

与上面相同的直方图在 Prometheus 中的累积直方图如下所示:

可以看到在 Prometheus 中直方图的计数是累计的,这是很奇怪的,因为通常情况下非累积的直方图更容易理解。Prometheus 为什么要这么做呢?想象一下,如果直方图指标中加入了额外的标签,或者划分了更多的 bucket,那么样本数据的分析就会变得越来越复杂,如果直方图是累积的,在抓取指标时就可以根据需要丢弃某些 bucket,这样可以在降低 Prometheus 维护成本的同时,还可以粗略计算样本值的分位数。通过这种方法,用户不需要修改应用代码,便可以动态减少抓取到的样本数量。另外直方图还提供了 _sum 指标和 _count 指标,所以即使你丢弃了所有的 bucket,仍然可以通过这两个指标值来计算请求的平均响应时间。通过累积直方图的方式,还可以很轻松地计算某个 bucket 的样本数占所有样本数的比例。

我们在演示的 demo 服务中暴露了一个直方图指标 demo_api_request_duration_seconds_bucket,用于跟踪 API 请求时长的分布,由于这个直方图为每个跟踪的维度导出了 26 个 bucket,因此这个指标有很多时间序列。我们可以先来看下来自一个服务实例的一个请求维度组合的直方图,查询语句如下所示:

demo_api_request_duration_seconds_bucket{instance="demo-service-0:10000", method="POST", path="/api/bar", status="200", job="demo"}

正常我们可以看到 26 个序列,每个序列代表一个 bucket,由 le 标签标识:

直方图可以帮助我们了解这样的问题,比如"我有多少个请求超过了100ms的时间?" (当然需要直方图中配置了一个以 100ms 为边界的桶),又比如"我99%的请求是在多少延迟下完成的?",这类数值被称为百分位数或分位数。在 Prometheus 中这两个术语几乎是可以通用,只是百分位数指定在 0-100 范围内,而分位数表示在 0 和 1 之间,所以第 99 个百分位数相当于目标分位数 0.99。

如果你的直方图桶粒度足够小,那么我们可以使用 histogram_quantile(φ scalar, b instant-vector) 函数用于计算历史数据指标一段时间内的分位数。该函数将目标分位数 (0 ≤ φ ≤ 1) 和直方图指标作为输入,就是大家平时讲的 pxx,p50 就是中位数,参数 b 一定是包含 le 这个标签的瞬时向量,不包含就无从计算分位数了,但是计算的分位数是一个预估值,并不完全准确,因为这个函数是假定每个区间内的样本分布是线性分布来计算结果值的,预估的准确度取决于 bucket 区间划分的粒度,粒度越大,准确度越低。

回到我们的演示服务,我们可以尝试计算所有维度在所有时间内的第 90 个百分位数,也就是 90% 的请求的持续时间。

# BAD!
histogram_quantile(0.9, demo_api_request_duration_seconds_bucket{job="demo"})

但是这个查询方式是有一点问题的,当单个服务实例重新启动时,bucket 的 Counter 计数器会被重置,而且我们常常想看看现在的延迟是多少(比如在过去 5 分钟内),而不是整个时间内的指标。我们可以使用 rate() 函数应用于底层直方图计数器来实现这一点,该函数会自动处理 Counter 重置,又可以只计算每个桶在指定时间窗口内的平均增长。

我们可以这样去计算过去 5 分钟内第 90 个百分位数的 API 延迟:

# GOOD!
histogram_quantile(0.9, rate(demo_api_request_duration_seconds_bucket{job="demo"}[5m]))

这个查询就好很多了。

这个查询会显示每个维度(job、instance、path、method 和 status)的第 90 个百分点,但是我们可能对单独的这些维度并不感兴趣,想把他们中的一些指标聚合起来,这个时候我们可以在查询的时候使用 Prometheus 的 sum 运算符与 histogram_quantile() 函数结合起来,计算出聚合的百分位,假设在我们想要聚合的维度之间,直方图桶的配置方式相同(桶的数量相同,上限相同),我们可以将不同维度之间具有相同 le 标签值的桶加在一起,得到一个聚合直方图。然后,我们可以使用该聚合直方图作为 histogram_quantile() 函数的输入。

注意:这是假设直方图的桶在你要聚合的所有维度之间的配置是相同的,桶的配置也应该是相对静态的配置,不会一直变化,因为这会破坏你使用 histogram_quantile() 查看的时间范围内的结果。

练习:

1.构建一个查询,计算在 0.0001 秒内完成的 demo 服务 API 请求的总百分比,与过去 5 分钟内所有请求总数的平均值。

sum(rate(demo_api_request_duration_seconds_bucket{le="0.0001"}[5m]))
/
sum(rate(demo_api_request_duration_seconds_bucket{le="+Inf"}[5m])) * 100

或者可以使用下面的语句查询

sum(rate(demo_api_request_duration_seconds_bucket{le="0.0001"}[5m]))
/
 sum(rate(demo_api_request_duration_seconds_count[5m])) * 100

2.构建一个查询,计算 demo 服务 API 请求的第 50 个百分位延迟,按 status code 和 method 进行划分,在过去一分钟的平均值。

histogram_quantile(0.5, sum by(status, method, le) (rate(demo_api_request_duration_seconds_bucket[1m])))

数据对比

有的时候我们可能需要去访问过去的数据,并和当前数据进行对比。例如,我们可能想比较今天的请求率和一周前的请求率之间的差异。我们可以在任何区间向量或瞬时向量选择器上附加一个偏移量 offset<duration> 的修饰符(比如 my_metric offset 5m 或者 my_metric[1m] offset 7d)。

让我们来看一个示例,在我们的 demo 服务中暴露了一个 Counter 指标 demo_items_shipped_total,该指标追踪物品的运输情况,用 5 分钟来模拟"每日"流量周期,所以我们不必等待一整天才能查看该时段的数据。

我们只使用第一个演示服务实例来测试即可,首先我们来看看它的速率:

rate(demo_items_shipped_total{instance="demo-service-0:10000"}[1m])

该服务还暴露了一个 0 或 1 的布尔指标,告诉我们现在是否是假期:

将假期与发货商品率进行比较,注意到节假日时它会减少!我们可以尝试将当前的发货速度与 7"天"(7 * 5 分钟)前的速度进行比较,看看是否有什么不正常的情况。

rate(demo_items_shipped_total{instance="demo-service-0:10000"}[1m])
/
rate(demo_items_shipped_total{instance="demo-service-0:10000"}[1m] offset 35m)

通常情况下,该比率约为 1,但当当天或前一天是假期时,我们得到的比率比正常情况下要略低或高。

但是,如果原因只是假期,我们想忽略这个较低或较高的比率。我们可以在过去或现在是假期的时候过滤掉这个比率,方法是附加一个 unless 集合操作符。

(
    rate(demo_items_shipped_total{instance="demo-service-0:10000"}[1m])
  /
    rate(demo_items_shipped_total{instance="demo-service-0:10000"}[1m] offset 35m)
)
unless
  (
    demo_is_holiday == 1  # Is it currently a holiday?
  or
    demo_is_holiday offset 35m == 1  # Was it a holiday 7 "days" ago?
  )

或者另外一种方法,我们只需要比较今天和一周前是否有相同的节日:

(
    rate(demo_items_shipped_total{instance="demo-service-0:10000"}[1m])
  /
    rate(demo_items_shipped_total{instance="demo-service-0:10000"}[1m] offset 35m)
)
unless
  (
      demo_is_holiday
    !=
      demo_is_holiday offset 35m
  )

练习:

1.构建一个查询,计算每个 path 路径的总请求率和 35 分钟前的差异。

  sum by(path) (rate(demo_api_request_duration_seconds_count[5m])) -
  sum by(path) (rate(demo_api_request_duration_seconds_count[5m] offset 35m))

检测

本节我们将学习如何来检查我们的实例数据抓取健康状况。

检查抓取实例

每当 Prometheus 抓取一个目标时,它都会存储一个合成的样本,其中包含指标名称 up 和被抓取实例的 job 和 instance 标签,如果抓取成功,则样本的值被设置为 1,如果抓取失败,则设置为 0,所以我们可以通过如下所示的查询来获取当前哪些实例处于正常或挂掉的状态:

up{job="demo"}

正常三个演示服务实例都处于正常状态,所以应该都为1。如果我们将第一个实例停掉,重新查询则第一个实例结果为0:

如果只希望显示 down 掉的实例,可以通过过滤0值来获取:

up{job="demo"} == 0

或者获取挂掉实例的总数:

count by(job) (up{job="demo"} == 0)

一般情况下这种类型的查询会用于指标抓取健康状态报警。

注意:因为 count() 是一个聚合运算符,它期望有一组维度的时间序列作为其输入,并且可以根据 bywithout 子句将输出序列分组。任何输出组只能基于现有的输入序列,如果根本没有输入序列,就不会产生输出。

检查序列数据

在某些情况下,只查看序列的样本值是不够的,有时还需要检测是否存在某些序列,上面我们用 up{job="demo"} == 0 语句来查询所有无法抓取的演示服务实例,但是只有已经被抓取的目标才会被加上 up 指标,如果 Prometheus 都没有抓取到任何的演示服务目标应该怎么办呢?比如它的抓取配置出问题了,服务发现可能返回也为空,或者由于 Prometheus 自身出了某些问题。

在这种情况下,absent() 函数就非常有用了,absent() 将一个瞬时向量作为其输入,当输入包含序列时,将返回一个空结果,不包含时将返回单个输出序列,而且样本值为1。

例如,查询语句 absent(up{job="demo"}) 将得到一个空的输出结果,如果测试一个没有被抓取的 job 是否存在的时候,将得到样本值1。

这可以帮助我们检测序列是否存在的情况。此外还有一个 absent() 的变种,叫做 absent_over_time(),它接受一个区间向量,告诉你在该输入向量的整个时间范围内是否有样本。

练习:

1.构建一个查询,检测指标 demo_api_request_duration_seconds_count 是否具有 PUT 的 method 标签的序列。

 absent(demo_api_request_duration_seconds_count{method="PUT"})

2.构建一个查询,当过去一小时内任务 non-existent 没有记录 up 指标时,该查询输出一个系列。

absent_over_time(up{job="non-existent"}[1h])

PromQL基础介绍到此结束,后续分享实际案例。

本文来自:知乎

感谢作者:知乎

查看原文:Prometheus基础相关--PromQL 基础(4)

1080 次点击  
加入收藏 微博
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传