运维监控系统实战笔记--PromQL有哪些常见的使用场景?(第六天)

文章来自“极客时间”  运维监控系统实战笔记

 PromQL 是 Prometheus 的查询语言,使用起来非常灵活方便,但很多人不知道如何更好地利用它,发挥不出它的优势。PromQL 主要用于时序数据的查询和二次计算场景。

时序数据

可以把时序数据理解成一个以时间为轴的矩阵


^
│     . . . . . . . . . .   node_load1{host="host01",zone="bj"}
│     . . . . . . . . . .   node_load1{host="host02",zone="sh"}
│     . . . . . . . . . .   node_load1{host="host11",zone="sh"}
v
<------- 时间 ---------->

每一个点称为一个样本(sample),样本由三部分组成。

(1)指标(metric):metric name 和描述当前样本特征的 labelsets。

(2)时间戳(timestamp):一个精确到毫秒的时间戳。

(3)值(value):表示该时间样本的值。PromQL 就是对这样一批样本数据做查

PromQL 典型的应用场景

PromQL 典型的应用场景就是时序数据的查询和二次计算,这也是 PromQL 的两个核心价值,其中查询操作靠的就是查询选择器,

  • 查询选择器

时序数据至少都有成千上万条,而每个监控图表的渲染或者每条告警规则的处理,都只是针对有限的几条数据,所以 PromQL 第一个需求就是过滤。

假设我有两个需求,一是查询上海所有机器 1 分钟的负载,二是查询所有以 host0 为前缀的机器 1 分钟的负载。PromQL 的写法是怎样的呢?


# 通过 = 来做 zone 的匹配过滤
node_load1{zone="sh"}
# 通过 =~ 来做 host 的正则过滤
node_load1{host=~"host0.*"}

大括号里写过滤条件,主要是针对标签过滤,操作符除了等于号和正则匹配之外,还有不等于 != 和正则非 !~。需要注意的是,metric name 也是一个非常重要的过滤条件,可以写到大括号里,比如我想同时查看上海机器的 load1、load5、load15 三个指标,可以对 __name__,也就是 metric 名字做正则过滤。


{__name__=~"node_load.*", zone="sh"}

我给出的 3 条 PromQL 都叫做即时查询(Instant Query),返回的内容叫做即时向量( Instant Vector)。因为是即时,如果当时没有数据,它会往前追溯,找到一个时间点。这个往回追溯的参数的值由 Prometheus 的启动参数 --query.lookback-delta 控制,这个参数默认是  5 分钟。从监控的角度来看,建议调短一些,比如改成 1 分钟 --query.lookback-delta=1m

除了即时查询,PromQL 中还有一种查询,叫做范围查询(Range Query),返回的内容叫做 Range Vector,比如下面的 PromQL。


{__name__=~"node_load.*", zone="sh"}[1m]

这个范围就是1分钟,采集的多少个点都会返回

Prometheus 官方文档会介绍各个函数的使用方法的时候,都会讲解函数参数

 PromQL 第一个核心价值——筛选。接下来我们看 PromQL 的另一个核心价值——计算。计算部分内容比较多,有算术、比较、逻辑、聚合运算符等,

  • 算术运算符

算术运算符比较简单,就是我们常用的加减乘除、取模之类的符号。


# 计算内存可用率,就是内存可用量除以内存总量,又希望按照百分比呈现,所以最后乘以100
mem_available{app="clickhouse"} / mem_total{app="clickhouse"} * 100
# 计算北京区网口出向的速率,原始数据的单位是byte,网络流量单位一般用bit,所以乘以8
irate(net_sent_bytes_total{zone="beijing"}[1m]) * 8

  • 比较运算符

比较运算符就是大于、小于、等于、不等于之类的,理解起来也比较简单,但是意义重大,告警规则的逻辑就是靠比较运算符来支撑的。这

例一:


mem_available{app="clickhouse"} / mem_total{app="clickhouse"} * 100 < 20
irate(net_sent_bytes_total{zone="beijing"}[1m]) * 8 / 1024 / 1024 > 700

内存可用率的告警,在 Prometheus 中可以这样配置。


groups:
- name: host
  rules:
  - alert: MemUtil
    expr: mem_available{app="clickhouse"} / mem_total{app="clickhouse"} * 100 < 20
    for: 1m
    labels:
      severity: warn
    annotations:
      summary: Mem available less than 20%, host:{{ $labels.ident }}

  • 逻辑运算符

逻辑运算符有 3 个,and、or 和 unless,用于 instant-vector 之间的运算。and 是求交集,or 是求并集,unless 是求差集。

关于磁盘的使用率问题,有的分区很大,比如 16T,有的分区很小,比如 50G,像这种情况如果只是用磁盘的使用率做告警就不太合理,比如 disk_used_percent{app="clickhouse"} > 70 表示磁盘使用率大于 70% 就告警。对于小盘,这个策略是合理的,但对于大盘,70% 的使用率表示还有非常多的空间,就不太合理。


disk_used_percent{app="clickhouse"} > 70 and disk_total{app="clickhouse"}/1024/1024/1024 < 200

  • 向量匹配

向量之间的操作是想要在右侧的向量中,为左侧向量的每个条目找到一个匹配的元素,匹配行为分为:one-to-one、many-to-one、one-to-many。

磁盘使用率的例子,就是典型的 one-to-one 类型

此时我们可以使用关键字 on 和 ignoring 来限制用于做匹配的标签集。


mysql_slave_status_slave_sql_running == 0
and ON (instance)
mysql_slave_status_master_server_id > 0

这个 PromQL 想表达的意思是如果这个 MySQL 实例是个 slave(master_server_id>0),就检查其 slave_sql_running 的值,如果 slave_sql_running==0,就表示 slave sql 线程没有在运行。

但 mysql_slave_status_slave_sql_running 和 mysql_slave_status_master_server_id 这两个 metric 的标签,可能并非完全一致。不过好在二者都有个 instance 标签,且相同的 instance 标签的数据从语义上来看就表示一个实例的多个指标数据,那我们就可以用关键字 on 来指定只使用 instance 标签做匹配,忽略其他标签。

与 on 相反的是关键字 ignoring,顾名思义,ignoring 是忽略掉某些标签,用剩下的标签来做匹配。


## example series
method_code:http_errors:rate5m{method="get", code="500"}  24
method_code:http_errors:rate5m{method="get", code="404"}  30
method_code:http_errors:rate5m{method="put", code="501"}  3
method_code:http_errors:rate5m{method="post", code="500"} 6
method_code:http_errors:rate5m{method="post", code="404"} 21
method:http_requests:rate5m{method="get"}  600
method:http_requests:rate5m{method="del"}  34
method:http_requests:rate5m{method="post"} 120

## promql
method_code:http_errors:rate5m{code="500"}
/ ignoring(code)
method:http_requests:rate5m

## result
{method="get"}  0.04            //  24 / 600
{method="post"} 0.05            //   6 / 120

难理解的是 one-to-many 和 many-to-one,这种情况下,做指标运算时就要借助关键字 group_left 和 group_right 了。left、right 指向高基数那一侧的向量。还是用上面 method_code:http_errors:rate5m 和 method:http_requests:rate5m 这两个指标来举例,你可以看一下使用 group_left 的 PromQL 和输出的结果。


## promql
method_code:http_errors:rate5m
/ ignoring(code) group_left
method:http_requests:rate5m

## result
{method="get", code="500"}  0.04            //  24 / 600
{method="get", code="404"}  0.05            //  30 / 600
{method="post", code="500"} 0.05            //   6 / 120
{method="post", code="404"} 0.175           //  21 / 120

比如针对 method="get" 的条目,右侧的向量中只有一个记录,但是左侧的向量中有两个记录,所以高基数的一侧是左侧,故而使用 group_left。

这里我再举一个例子,来说明 group_left、group_right 的一个常见用法。比如我们使用 kube-state-metrics 来采集 Kubernetes 各个对象的指标数据,其中针对 pod 有个指标是 kube_pod_labels,该指标会把 pod 的一些信息放到标签里,指标值是 1,相当于一个元信息。kube_pod_labels{[...] label_name="frontdoor", label_version="1.0.1", label_team="blue" namespace="default", pod="frontdoor-xxxxxxxxx-xxxxxx",} = 1


kube_pod_labels{
[...]
  label_name="frontdoor",
  label_version="1.0.1",
  label_team="blue"
  namespace="default",
  pod="frontdoor-xxxxxxxxx-xxxxxx",
} = 1

假设某个 Pod 是接入层的,统计了很多 HTTP 请求相关的指标,我们想统计 5xx 的请求数量,希望能按 Pod 的 version 画一个饼图。这里有个难点:接入层的请求类指标没有 version 标签,version 信息只出现在 kube_pod_labels 里,那怎么让二者联动呢?


sum(
  rate(http_request_count{code=~"^(?:5..)$"}[5m])) by (pod)  

on (pod) group_left(label_version) kube_pod_labels

然后我们乘以 kube_pod_labels,这个值是 1。任何值乘以 1 都是原来的值,所以对整体数值没有影响,而 kube_pod_labels 有多个标签,而且和 sum 语句的结果向量的标签不一致,所以通过 on(pod) 语法来指定只按照 pod 标签来建立对应关系。最后,利用 group_left(label_version),把 label_version 附加到了结果向量里,高基数的部分显然是 sum 的部分,所以使用 group_left 而非 group_right。

  • 聚合运算

除了前面我们说的查询需求外,针对单个指标的多个 series,还会有一些聚合需求。比如说,我想查看 100 台机器的平均内存可用率,或者想要排个序,取数值最小的 10 台。这种需求可以使用 PromQL 内置的聚合函数来实现。


# 求取 clickhouse 的机器的平均内存可用率
avg(mem_available_percent{app="clickhouse"})

# 把 clickhouse 的机器的内存可用率排个序,取最小的两条记录
bottomk(2, mem_available_percent{app="clickhouse"})

另外,我们有时会有分组统计的需求,比如我想分别统计 clickhouse 和 canal 的机器内存可用率,可以使用关键字 by 指定分组统计的维度(与 by 相反的是 without)。


avg(mem_available_percent{app=~"clickhouse|canal"}) by (app) 

注意:这些聚合运算,可以理解为纵向拟合。相当于同一时间就相当于把每个时刻的 100 个点拟合成 1 个点。

还有一类聚合运算函数,可以看作是横向拟合,也就是 <aggregation>_over_time 类的函数。这些函数接收范围向量,因为范围向量是一个时段内有多个值,<aggregation> 就是对这多个值做运算。


max_over_time(target_up[2m])

target_up 指标后面加了 [2m],指的就是获取这个指标最近 2 分钟的所有数据点,如果 15 秒采集一个点,2 分钟就是 8 个点,max_over_time 就是对这 8 个点求最大值,相当于对各个时间序列做横向拟合。

  • 容易误解的函数

Prometheus 还内置了很多其他函数,其中用得最广并且最容易被人误解的是 increase 和 rate 函数,

 increase 函数,字面意思上表示求取一个增量,接收一个 range-vector,range-vector 显然是会返回多个 value+timestamp 的组合。我们直观地理解就是,直接把时间范围内的最后一个值减去第一个值,不就可以得到增量了吗?非也!Prometheus 只能基于现有的数据做外推,也就是使用最后一个点的数值减去第一个点的数值,得到的结果除以时间差,再乘以 60。

说一下 rate 函数,increase 函数是求取的时间段内的增量,而且有数据外推,rate 函数则求取的是每秒变化率,也有数据外推的逻辑,increase 的结果除以 range-vector 的时间段的大小,就是 rate 的值。


rate(net_bytes_recv{interface="eth0"}[1m])
== bool
increase(net_bytes_recv{interface="eth0"}[1m])/60.0

这个表达式永远都会返回 1,即等号前后的两个 PromQL 语义上是相同的。

rate 函数求取的变化率,相对平滑。因为是拿时间范围内的最后一个值和第一个值做数据外推,一些毛刺现象就会被平滑掉。如果想要得到更敏感的数据,我们可以使用 irate 函数。irate 是拿时间范围内的最后两个值来做计算,变化就会更剧烈,我们拿网卡入向流量这个指标来做个对比。

小结

讲解了 PromQL 的两个核心价值,一个是筛选,一个是计算。筛选是靠查询选择器,查询分为即时查询和范围查询。计算部分内容较多,有算术、比较、逻辑、聚合运算符,还有向量匹配逻辑,特别是 group_left 和 group_right,比较难理解,需要你仔细推敲。

函数部分比较重要的是 increase 和 rate,其实 histogram_quantile 也很常用而且比较难理解,不过我们在前面的第 2 讲中已经介绍过 histogram_quantile 的计算逻辑了,这里就不再重复。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值