【Prometheus】PromQL万字详解
在上⼀⽂当中,通过Node Exporter暴露的HTTP服务,Prometheus可以采集到当前主机所有监控指标的样本数据。例如:
# HELP node_cpu Seconds the cpus spent in each mode.
# TYPE node_cpu counter
node_cpu{cpu="cpu0",mode="idle"} 362812.7890625
# HELP node_load1 1m load average.
# TYPE node_load1 gauge
node_load1 3.0703125
其中⾮#开头的每⼀⾏表⽰当前Node Exporter采集到的⼀个监控样本:node_cpu和node_load1表明了当前指标的名称、⼤括号中的标签则反映了当前样本的⼀些特征和维度、浮点数则是该监控样本的具体值。
样本
Prometheus会将所有采集到的样本数据以时间序列(time-series)的⽅式保存在内存数据库中,并且定时保存到硬盘上。time-series是按照时间戳和值的序列顺序存放的,我们称之为向量(vector). 每条time-series通过指标名称(metrics name)和⼀组标签集(labelset)命名。如下所⽰,可以将time-series理解为⼀个以时间为Y轴的数字矩阵:
^
│  . . . . . . . . . . . . . . . . .  . .  node_cpu{cpu="cpu0",mode="idle"}
│    . . . . . . . . . . . . . . . . . . .  node_cpu{cpu="cpu0",mode="system"}
│    . . . . . . . . . .  . . . . . . . .  node_load1{}
│    . . . . . . . . . . . . . . . .  . .
v
<------------------ 时间 ---------------->
在time-series中的每⼀个点称为⼀个样本(sample),样本由以下三部分组成:
指标(metric):metric name和描述当前样本特征的labelsets;
时间戳(timestamp):⼀个精确到毫秒的时间戳;
样本值(value): ⼀个float64的浮点型数据表⽰当前样本的值。
sql语句实现的四种功能<--------------- 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
指标(Metric)
在形式上,所有的指标(Metric)都通过如下格式标⽰:
<metric name>{<label name>=<label value>, ...}
指标的名称(metric name)可以反映被监控样本的含义(⽐如,http_request_total - 表⽰当前系统接收到的HTTP请求总量)。指标名称只能由ASCII字符、数字、下划线以及冒号组成并必须符合正则表达式[a-zA-Z_:][a-zA-Z0-9_:]*。
标签(label)反映了当前样本的特征维度,通过这些维度Prometheus可以对样本数据进⾏过滤,聚合等。标签的名称只能由ASCII字符、数字以及下划线组成并满⾜正则表达式[a-zA-Z_][a-zA-Z0-9_]*。
其中以__作为前缀的标签,是系统保留的关键字,只能在系统内部使⽤。标签的值则可以包含任何Unicode编码的字符。在Prometheus 的底层实现中指标名称实际上是以__name__=<metric name>的形式保存在数据库中的,因此以下两种⽅式均表⽰的同⼀条time-series:
api_http_requests_total{method="POST", handler="/messages"}
等同于:
{__name__="api_http_requests_total",method="POST", handler="/messages"}
在Prometheus源码中也可以到指标(Metric)对应的数据结构,如下所⽰:
type Metric LabelSet
type LabelSet map[LabelName]LabelValue
type LabelName string
type LabelValue string
再来举个例⼦,⼀条 Prometheus 数据由⼀个指标名称(metric)和 N 个标签(label,N >= 0)组成的,⽐如下⾯这个例⼦:promhttp_metric_handler_requests_total{code="200",instance="192.168.0.107:9090",job="prometheus"} 106
这条数据的指标名称为 promhttp_metric_handler_requests_total,并且包含三个标签 code、instance 和 job,这条记录的值为 106。上⾯说过,Prometheus 是⼀个时序数据库,相同指标相同标签的数据构成⼀条时间序列。如果以传统数据库的概念来理解时序数据库,可以把指标名当作表名,标签是字
段,timestamp 是主键,还有⼀个 float64 类型的字段表⽰值(Prometheus ⾥⾯所有值都是按 float64 存储)。
这种数据模型和 OpenTSDB 的数据模型是⽐较类似的,详细的信息可以参考官⽹⽂档 Data model。另外,关于指标和标签的命名,官⽹有⼀些指导性的建议,可以参考 Metric and label naming 。
Metrics类型
在上⼀⼩节中我们带领读者了解了Prometheus的底层数据模型,在Prometheus的存储实现上所有的监控样本都是以time-series的形式保存在Prometheus内存的TSDB(时序数据库)中,⽽time-series所对应的监控指标(metric)也是通过labelset进⾏唯⼀命名的。
从存储上来讲所有的监控指标metric都是相同的,但是在不同的场景下这些metric⼜有⼀些细微的差异。 例如,在Node Exporter返回的样本中指标node_load1反应的是当前系统的负载状态,随着时间的变化这个指标返回的样本数据是在不断变化的。⽽指标node_cpu所获取到的样本数据却不同,它是⼀个持续增⼤的值,因为其反应的是CPU的累积使⽤时间,从理论上讲只要系统不关机,这个值是会⽆限变⼤的。
虽然 Prometheus ⾥存储的数据都是 float64 的⼀个数值,为了能够帮助⽤户理解和区分这些不同监控
指标之间的差异,Prometheus按类型来分定义了4种不同的指标类型(metric type),可以把 Prometheus 的数据分成四⼤类:
Counter
Gauge
Histogram
Summary
在Exporter返回的样本数据中,其注释中也包含了该样本的类型。例如:
# HELP node_cpu Seconds the cpus spent in each mode.
# TYPE node_cpu counter
node_cpu{cpu="cpu0",mode="idle"} 362812.7890625
Counter ⽤于计数,例如:请求次数、任务完成数、错误发⽣次数,这个值会⼀直增加,不会减少。Gauge 就是⼀般的数值,可⼤可⼩,例如:温度变化、内存使⽤变化。Histogram 是直⽅图,或称为
柱状图,常⽤于跟踪事件发⽣的规模,例如:请求耗时、响应⼤⼩。
它特别之处是可以对记录的内容进⾏分组,提供 count 和 sum 的功能。Summary 和 Histogram ⼗分相似,也⽤于跟踪事件发⽣的规模,不同之处是,它提供了⼀个 quantiles 的功能,可以按百分⽐划分跟踪的结果。例如:quantile 取值 0.95,表⽰取采样值⾥⾯的95% 数据。更多信息可以参考官⽹⽂档 Metric types,Summary 和 Histogram 的概念⽐较容易混淆,属于⽐较⾼阶的指标类型,可以参考 Histograms and summaries 这⾥的说明。
这四种类型的数据只在指标的提供⽅作区分,也就是上⾯说的 Exporter,如果你需要编写⾃⼰的 Exporter 或者在现有系统中暴露供Prometheus 抓取的指标,你可以使⽤ Prometheus client libraries,这个时候你就需要考虑不同指标的数据类型了。如果你不⽤⾃⼰实现,⽽是直接使⽤⼀些现成的 Exporter,然后在 Prometheus ⾥查查相关的指标数据,那么可以不⽤太关注这块,不过理解Prometheus 的数据类型,对写出正确合理的 PromQL 也是有帮助的。
学习 PromQL
通过上⾯的步骤安装好 Prometheus 之后,我们现在可以开始体验 Prometheus 了。Prometheus 提供了可视化的 Web UI ⽅便我们操作,直接访问 localhost:9090/ 即可,它默认会跳转到 Graph 页⾯:
第⼀次访问这个页⾯可能会不知所措,我们可以先看看其他菜单下的内容,⽐如:Alerts 展⽰了定义的所有告警规则,Status 可以查看各种 Prometheus 的状态信息,有 Runtime & Build Information、Command-Line Flags、Configuration、Rules、Targets、Service Discovery 等等。
实际上 Graph 页⾯才是 Prometheus 最强⼤的功能,在这⾥我们可以使⽤ Prometheus 提供的⼀种特殊表达式来查询监控数据,这个表达式被称为 PromQL(Prometheus Query Language)。通过 PromQL 不仅可以在 Graph 页⾯查询数据,⽽且还可以通过Prometheus 提供的 HTTP API 来查询。查询的监控数据有列表和曲线图两种展现形式(对应上图中 Console 和 Graph 这两个标签)。
我们上⾯说过,Prometheus ⾃⾝也暴露了很多的监控指标,也可以在 Graph 页⾯查询,展开 Execute 按钮旁边的下拉框,可以看到很多指标名称,我们随便选⼀个,譬如:promhttp_metric_handler_requests_total,这个指标表⽰ /metrics 页⾯的访问次数,Prometheus 就是通过这个页⾯来抓取⾃⾝的监控数据的。在 Console 标签中查询结果如下:
上⾯在介绍 Prometheus 的配置⽂件时,可以看到 scrape_interval 参数是 15s,也就是说 Prometheus 每 15s 访问⼀次 /metrics 页⾯,所以我们过 15s 刷新下页⾯,可以看到指标值会⾃增。在 Graph 标签中可以看得更明显:
我们从⼀些例⼦开始学习 PromQL,最简单的 PromQL 就是直接输⼊指标名称,⽐如:
# 表⽰ Prometheus 能否抓取 target 的指标,⽤于 target 的健康检查
up
这条语句会查出 Prometheus 抓取的所有 target 当前运⾏情况,譬如下⾯这样:
up{instance="192.168.0.107:9090",job="prometheus"}    1
up{instance="192.168.0.108:9090",job="prometheus"}    1
up{instance="192.168.0.107:9100",job="server"}    1
up{instance="192.168.0.108:9104",job="mysql"}    0
也可以指定某个 label 来查询:
up{job="prometheus"}
这种写法被称为 Instant vector selectors,这⾥不仅可以使⽤ = 号,还可以使⽤ !=、=~、!~,⽐如下⾯这样:
up{job!="prometheus"}
up{job=~"server|mysql"}
up{job=~"192\.168\.0\.107.+"}
=~ 是根据正则表达式来匹配,必须符合 RE2 的语法。
和 Instant vector selectors 相应的,还有⼀种选择器,叫做 Range vector selectors,它可以查出⼀段时间内的所有数据:
http_requests_total[5m]
这条语句查出 5 分钟内所有抓取的 HTTP 请求数,注意它返回的数据类型是 Range vector(区间向量),没办法在 Graph 上显⽰成曲线图,⼀般情况下,会⽤在 Counter 类型的指标上,并和 rate() 或 irate() 函数⼀起使⽤(注意 rate 和 irate 的区别)。
# 计算的是每秒的平均值,适⽤于变化很慢的 counter
# per-second average rate of increase, for slow-moving counters
rate(http_requests_total[5m])
# 计算的是每秒瞬时增加速率,适⽤于变化很快的 counter
# per-second instant rate of increase, for volatile and fast-moving counters
irate(http_requests_total[5m])
此外,PromQL 还⽀持 count、sum、min、max、topk 等 聚合操作,还⽀持 rate、abs、ceil、floor 等⼀堆的 内置函数,更多的例⼦,还是上官⽹学习吧。如果感兴趣,我们还可以把 PromQL 和 SQL 做⼀个对⽐,会发现 PromQL 语法更简洁,查询性能也更⾼。
除了使⽤m表⽰分钟以外,PromQL的时间范围选择器⽀持其它时间单位:
s - 秒
m - 分钟
h - ⼩时
d - 天
w - 周
y - 年
时间位移操作
在瞬时向量表达式或者区间向量表达式中,都是以当前时间为基准:
http_request_total{} # 瞬时向量表达式,选择当前最新的数据
http_request_total{}[5m] # 区间向量表达式,选择以当前时间为基准,5分钟内的数据
⽽如果我们想查询,5分钟前的瞬时样本数据,或昨天⼀天的区间内的样本数据呢? 这个时候我们就可以使⽤位移操作,位移操作的关键字为offset。
可以使⽤offset时间位移操作:
http_request_total{} offset 5m
http_request_total{}[1d] offset 1d
使⽤聚合操作
⼀般来说,如果描述样本特征的标签(label)在并⾮唯⼀的情况下,通过PromQL查询数据,会返回多条满⾜这些特征维度的时间序列。⽽PromQL提供的聚合操作可以⽤来对这些时间序列进⾏处理,形成⼀条新的时间序列:
# 查询系统所有http请求的总量
sum(http_request_total)
# 按照mode计算主机CPU的平均使⽤时间
avg(node_cpu) by (mode)
# 按照主机查询各个主机的CPU使⽤率
sum(sum(irate(node_cpu{mode!='idle'}[5m]))  / sum(irate(node_cpu[5m]))) by (instance)
标量和字符串
除了使⽤瞬时向量表达式和区间向量表达式以外,PromQL还直接⽀持⽤户使⽤标量(Scalar)和字符串(String)。
标量(Scalar):⼀个浮点型的数字值
标量只有⼀个数字,没有时序。
例如:
10
需要注意的是,当使⽤表达式count(http_requests_total),返回的数据类型,依然是瞬时向量。⽤户可以通过内置函数scalar()将单个瞬时向量转换为标量。
字符串(String):⼀个简单的字符串值
直接使⽤字符串,作为PromQL表达式,则会直接返回字符串。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。