头图

前言

在grafana上依葫芦画瓢写过很多PromQL,复制粘贴,却依旧懵逼。看过很多博客,文档,还是不解其意。这些教程都是直接告诉我们有哪些函数,语法是什么,看完之后还是难以理解,比如说:

  • [1m]是什么意思?为什么有的函数需要有的不需要?它对grafana上的展示数据有什么影响?
  • rate和irate的区别是什么?
  • sum和rate的使用有没有先后顺序?

这篇文章会对这几个问题做出解答,但是不会深入讲解Prometheus的数据存储方式,也不会深入介绍PromQL的每一个API的实现,本文的重点是希望帮助大家写出合理的,放心的PromQL,让我们的监控可以更准确地发现系统中的问题。

理解时间序列

Metric

metric就是指要监控的目标,在形式上,所有的metric都通过如下格式表示:

<metric name>{<label name>=<label value>, ...} 

metric name一般表示的是被监控样本的真实含义,比如:http_request_total,表示当前系统接收到的http请求总量。标签(label)反映了当前样本的特征维度,通过这些纬度可以对样本进行过滤,聚合等。

sample

Prometheus会定时到指定的Exportor上pull当前的样本数据,然后根据pull的时间以时间序列的方式保存在内存数据中,并且定时持久化到硬盘上,Exportors只维护指标的值。每条时间序列由指标名称(metrics)和一组标签集(lableset)确定,也就是说一个指标名称可能对应很多条时间序列。

可以将时间序列理解为一个以时间为横轴,值为竖轴的二维坐标系:

  │     . . . . . . . . . .   node_cpu{cpu="cpu0",mode="idle"}
  │     . . . . . . . . . .   node_cpu{cpu="cpu0",mode="system"}
  │     . . . . . . . . . .   node_load1{}
  v
    <------- 时间 ---------->

每个样本点都获取三部分信息:

  • 指标(metric): metric name + labelsets
  • 时间戳(timestamp): 精确到毫秒的时间戳,注意,这里指的是pull的时间,而不是生成value的时间。所以,监控是有一定滞后性的
  • 值(value): 表示该时间的样本的值
<--------------- metric ---------------------><-timestamp -><-value->
http_request_total{status="200", method="GET"}@1434417560938 => 94355
http_request_total{status="200", method="GET"}@1434417561287 => 94334
http_request_total{status="404", method="GET"}@1434417560938 => 38473
http_request_total{status="404", method="GET"}@1434417561287 => 38544
http_request_total{status="200", method="POST"}@1434417560938 => 4748
http_request_total{status="200", method="POST"}@1434417561287 => 4785

查询语法

在阅读这部分的时候,建议先不要联想Gafana中怎么使用这些查询表达式,因为PromQL对于Grafna来说,和SQL没有什么区别,都是查询出结果,然后根据各种指定配置来进行绘图。我们要重点关注的是,PromQL的查询结果是怎么样的。

指标查询(instant vector)

通过指标名和标签进行查询,可以查询该指标下所有的时间序列中距离当前系统时间最新的值。这个值是没有时间概念的,所以查询的结果被称为瞬时向量(instant vector),如下图所示:

  • 标签过滤支持使用=和!=两种完全匹配的形式
  • 支持使用正则表达作为匹配条件,多个正则表达式之间使用|进行分离

如果想统计多个环境下的请求数统计,可以使用如下表达式:

http_requests_total{environment=~"product|test|development",method!="GET"}

如果想统计多个job的请求统计数,可以使用如下的表达式:

http_requests_total{environment="product",method!="GET", job=~"cs.*"}

时间范围查询(区间向量查询)

如果我们想采集过去一段时间范围内的样本数据时,我们则需要使用区间向量表达式。区间向量表达式和瞬时向量表达式之间的差异在于需要定义时间范围,通过时间范围选择器[]进行定义。例如查询距离当前时间最近5分钟内的所有样本数据:

可以看到结果中,每个时间序列序列都有5个值,并且@了五个不同的时间戳。说明该prometheus每分钟pull一次,所以5分钟有5个结果。除了使用m表示分钟以外,PromQL的时间范围选择器还支持其他时间单位:

  • s - 秒
  • m - 分钟
  • h - 小时
  • d - 天
  • w - 周
  • y - 年

聚合查询

查询可能会返回多条满足指定标签的时间序列,可能有时候我们并不希望分开查询,例如查询请求总数时想要的结果是:

http_requests_total{} 170

而不是:

http_requests_total{environment="product“} 30
http_requests_total{environment="test“} 60
http_requests_total{environment="developement“} 80

部分聚合

有时候,我们并不想完全聚合,而是想根据某个标签进行区分,可以使用by来进行拆分。比如说,我们的集群内有多个服务,每个服务有多台机器(CPU), 我们需要监控每个CPU累计的空闲时间,并设置时间序列的名称为cpu-{{cpu}}:

sum(node_cpu_seconds_total{mode=“idle”} )by (cpu)

内置函数

为了查询方便,Prometheus内置了一些函数来辅助计算,下面我们介绍一些典型的例子,首先是instant-vector的辅助函数:

  • 绝对值,abs(instant-vector),
  • 平方根,sqrt(instant-vector)
  • 指数计算,exp(instant-vector)
  • 自然对数,ln(instant-vector )
  • 向上取整,ceil(instant-vector)
  • 向下取整,floor(instant-vector)
  • 四舍五入取整,round(instant-vector)
  • 计算区间向量里最大最小的差值,delta(range-vector)
  • 计算区间向量里最后一个值和第一个值的差值,increase(range-vector)
  • 计算区间向量里的平均增长率,rate(range-vector)

然后是range-vector的辅助函数:

  • 指定间隔内所有点的平均值,avg_over_time(range-vector)
  • 指定间隔中所有点的最小值, min_over_time(range-vector)
  • 指定间隔内所有点的最大值,max_over_time(range-vector)
  • 指定时间间隔内所有值的总和,sum_over_time(range-vector)

Metric 类型

Prometheus根据目标和内容的不同,把metric分为了4种类型:Counter(计数器),Gauge(仪表盘),Histogram(直方图),Summary(摘要)。它们本质上都是指标,都是时间序列,只是进行了简单的分类,更方便沟通和理解。

Counter

Counter是一个永远只增不减的Metric类型,比如说服务器的服务请求数,服务器收到了多少数据包,这些数字都是自增不减的,用Counter最合适。每一个时间点的总请求数都会包含之前时间点的请求数,可以理解为是有状态的。使用counter记录每一个时间点的总数,然后除以时间,就可以得到QPS,packets/s等数据。

在Counter中,每一个时间点的请求中暑都会包含之前的时间点请求数,所以可以理解为它是一个“有状态的”的metric。

Gauge

Gauge是比较符合直觉的,它表示一个当前的”状态“,比如说当前内存是多少,CPU的当前使用率是多?

我们可以将Gauge理解为”无状态的“,即Gauge的metric不需要关心历史的值,只需要记录当前的值。

Histogram和Summary:数据分布

除了Counter和Gauge类型的监控之外,Prometheus还分别定义了Histogram和Summary的metric类型,主要用于统计和分析样本的分布情况。

在大多数情况下,我们都倾向于使用某些量化指标的平均值,例如CPU的平均使用率,页面的平均响应时间。但是这种方式的问题很明显,以系统API调用的平均响应时间为例:如果大多数API请求都维持在100ms的响应时间,而个别请求都响应时间需要5s,那么会导致某些web页面的响应时间落在中位数的情况,这种现象被称为长尾问题。

为了区分平均的慢还是长尾的慢,最简单的方式就是按照请求延迟的范围进行分组。例如,统计延迟在0ms到10ms请求数有多少,10ms到20ms之间的请求数有多少,通过这种方式可以快速分析系统慢点原因。

举个例子,Prometheus自身监控的指标prometheus_tsdb_wal_fsync_duration_seconds的metrics类型为summary, 通过访问Prometheus Server的metrics地址,可以获取到以下监控样本点数据:

# HELP prometheus_tsdb_wal_fsync_duration_seconds Duration of WAL fsync.
# TYPE prometheus_tsdb_wal_fsync_duration_seconds summary
prometheus_tsdb_wal_fsync_duration_seconds{quantile="0.5"} 0.012352463
prometheus_tsdb_wal_fsync_duration_seconds{quantile="0.9"} 0.014458005
prometheus_tsdb_wal_fsync_duration_seconds{quantile="0.99"} 0.017316173
prometheus_tsdb_wal_fsync_duration_seconds_sum 2.888716127000002
prometheus_tsdb_wal_fsync_duration_seconds_count 216

从上面的样本中可以得知当前Prometheus Server进行wal_fsync操作的总次数为216次,总耗时为2.888716127000002s,并且50%的耗时不超过0.012352463秒,90%的操作耗时不超过0.014458005s,99%的操作耗时不超过0.017316173s。

在Prometheus自身监控中,我们还能找到类型为Histogram的监控指标:prometheus_tsdb_compaction_chunk_range_bucket:

# HELP prometheus_tsdb_compaction_chunk_range Final time range of chunks on their first compaction
# TYPE prometheus_tsdb_compaction_chunk_range histogram
prometheus_tsdb_compaction_chunk_range_bucket{le="100"} 0
prometheus_tsdb_compaction_chunk_range_bucket{le="400"} 0
prometheus_tsdb_compaction_chunk_range_bucket{le="1600"} 0
prometheus_tsdb_compaction_chunk_range_bucket{le="6400"} 0
prometheus_tsdb_compaction_chunk_range_bucket{le="25600"} 0
prometheus_tsdb_compaction_chunk_range_bucket{le="102400"} 0
prometheus_tsdb_compaction_chunk_range_bucket{le="409600"} 0
prometheus_tsdb_compaction_chunk_range_bucket{le="1.6384e+06"} 260
prometheus_tsdb_compaction_chunk_range_bucket{le="6.5536e+06"} 780
prometheus_tsdb_compaction_chunk_range_bucket{le="2.62144e+07"} 780
prometheus_tsdb_compaction_chunk_range_bucket{le="+Inf"} 780
prometheus_tsdb_compaction_chunk_range_sum 1.1540798e+09
prometheus_tsdb_compaction_chunk_range_count 780

从上面的样本中可以得知当前Prometheus Server tsdb数据库的压缩后数据共有780个,总大小是1.1540798e+09,小于等于100/400/1600/.../409600的有0。

与Summary类型的指标相似之处在于Histogram类型的样本同样会反应当前指标的记录总数(以count作为后缀)以及其值的总量(sum作为后缀)。不同的是Histogram指标直接反应了在不同区间内的样本个数,区间通过标签len进行定义。

同时对于Histogram的指标,我们还可以通过histogram_quantile()函数计算出其值的分位数。Summary的分位数是由客户端计算,而Histogram则是在服务端直接计算分位数。

histogram_quantile(0.95, prometheus_tsdb_compaction_chunk_range_bucket)

参考

彻底理解Prometheus查询语法


CloudFish
0 声望0 粉丝

脂肪三尺,非一日之寒