大家好,我是小斐呀。
上一篇文章展开谈了谈对 Prometheus
的基础和架构学习,读完是不是觉得很枯燥,放心今天的篇幅还是有些枯燥,不过是讲解 PromQL
的理论基础。关于 Prometheus
基础和架构没有读过的朋友可以直接点击链接跳转到上一篇精读:Prometheus 监控笔记(2):架构知多少?
Prometheus
通过指标名称 (metrics name
) 以及对应的一组标签 (labelset
) 唯一定义一条时间序列。指标名称反映了监控样本的基本标识,而label则在这个基本特征上为采集到的数据提供了多种特征维度。用户可以基于这些特征维度过滤,聚合,统计从而产生新的计算后的一条时间序列。
PromQL
是 Prometheus
内置的数据查询语言,其提供对时间序列数据丰富的查询,聚合以及逻辑运算能力的支持。并且被广泛应用在 Prometheus
的日常应用当中,包括对数据查询、可视化、告警处理当中。可以这么说, PromQL
是 Prometheus
所有应用场景的基础,理解和掌握 PromQL
是 Prometheus
入门的第一课。
PromQL
是我们学习 Prometheus
最困难也是最重要的部分,本章节我们将介绍 PromQL
的基础知识、理论基础,然后会深入了解更加高级的查询模式。
当 Prometheus
从系统和服务收集指标数据时,它会把数据存储在内置的时序数据库 (TSDB
) 中,要对收集到的数据进行任何处理,我们都可以使用 PromQL
从 TSDB
中读取数据,同时可以对所选的数据执行过滤、聚合以及其他转换操作。
PromQL
的执行可以通过两种方式来触发:
Prometheus
服务器中,记录规则和警报规则会定期运行,并执行查询操作来计算规则结果(例如触发报警)。该执行在 Prometheus
服务内部进行,并在配置规则时自动发生。Prometheus
服务提供的 HTTP API
来执行 PromQL
查询。这就是仪表盘软件(例如 Grafana
、 PromLens
以及 Prometheus
内置 Web UI)访问 PromQL
的方式。HTTP API:https://prometheus.io/docs/prometheus/latest/querying/api/
针对 HTTP API
后续会单独开篇讲讲,可持续关注。
PromQL
可以用于许多监控场景,下面简单介绍几个相关案例。
临时查询:使用的最多,日常调试和诊断都会用到,而且验证表达式的正确性也有很大作用。
我们可以用 PromQL
来对收集的数据进行实时查询,这有助于我们去调试和诊断遇到的一些问题,我们一般也是直接使用内置的表达式查询界面来执行这类查询:
仪表盘:在自定义监控面板的时候是一定要使用的,故需要很熟悉。
同样我们也可以基于 PromQL
查询来创建可视化的图形、表格等面板,目前使用最广泛的就是 Grafana
:
Grafana
原生支持 Prometheus
作为数据源,并内置支持了 PromQL
表达式的查询。
报警:在对我们重点关注的服务做告警的时候,告警触发规则需要用到,这是特别重要的一点。
Prometheus
可以直接使用基于 PromQL
对收集的数据进行的查询结果来生成报警,一个完整的报警规则如下所示:
groups:
- name: demo-service-alerts
rules:
- alert: Many5xxErrors
expr: |
(
sum by(path, instance, job) (
rate(demo_api_request_duration_seconds_count{status=~"5..",job="demo"}[1m])
)
/
sum by(path, instance, job) (
rate(demo_api_request_duration_seconds_count{job="demo"}[1m])
) * 100 > 0.5
)
for: 30s
labels:
severity: critical
annotations:
title: '{{$labels.instance}} high 5xx rate on {{$labels.path}}'
description: 'The 5xx error rate for path {{$labels.path}} on {{$labels.instance}} is {{$value}}%.'
除了构成报警规则核心的 PromQL
表达式(上面 YAML 文件中的 expr
属性),报警规则还包含其他的一些元数据字段,后面在具体讲解报警的章节中会详细展开。
然后, Prometheus
可以通过一个名为 Alertmanager
的组件来发送报警通知,可以配置一些接收器来接收这些报警,比如用钉钉来接收报警:
自动化:结合监控查询的结果,自定义构建自动化流程。
此外我们还可以构建自动化流程,针对 PromQL
执行的查询结果来做出决策,比如 Kubernetes
中基于自定义指标的水平自动伸缩对象 HPA
。
Prometheus
基本上将所有数据存储为时间序列:属于同一数据指标和同一组标注维度的带有时间戳的数据流。除了存储的时间序列之外, Prometheus
可能会生成临时派生的时间序列作为查询的结果。下图展示了基本的数据模型图:
每个时间序列都由其 __名称__
和被称为 __标签__
的可选键值对唯一标识。
指标名称指定了所监测系统的一般功能(如 http_requests_total
- 收到 HTTP 请求总数)。它可能包含 ASCII
字符,数字下划线和冒号。它必须能被正则表达式 [a-zA-Z_:][a-zA-Z0-9_:]*
匹配。
注意:冒号是为用户自定义的规则保留的。exporter 或直接展示的组件都不应使用它们。
标签可以使 Prometheus
支持多维度数据模型:具有相同数据指标名称的标签的任何给定组合都可以标识该数据指标的特定维度实例(如,使用 POST
方法到 /api/tracks
处理程序的所有 HTTP 请求)。查询语言允许基于这些维度进行过滤和聚合。更改任何标签值,包括添加或删除,都会创建一个新的时间序列。
标签名称可以包含 ASCII
字符,数字和下划线。它必须能被正则表达式 [a-zA-Z_:][a-zA-Z0-9_:]*
匹配。以 __
开头的标签名称保留供内部使用。
标签值可以包含任何 Unicode
字符。
标签值为空的标签被认为等同于不存在的标签。
数据样本构成实际的时间序列数据。每个数据样本包括:
Prometheus
会将所有采集到的样本数据以时间序列的方式保存在内存数据库中,并且定时保存到硬盘上。时间序列是按照时间戳和值的序列顺序存放的,我们称之为向量 (vector
) ,每条时间序列通过指标名称 (metrics name
) 和一组标签集 (labelset
) 命名。如下所示,可以将时间序列理解为一个以时间为 X 轴的数字矩阵:
^
│ . . . . . . . . . . . . . . . . . . . node_cpu_seconds_total{cpu="cpu0",mode="idle"}
│ . . . . . . . . . . . . . . . . . . . node_cpu_seconds_total{cpu="cpu0",mode="system"}
│ . . . . . . . . . . . . . . . . . . node_load1{}
│ . . . . . . . . . . . . . . . . . .
v
<------------------ 时间 ---------------->
在时间序列中的每一个点称为一个样本 (sample
),样本由以下三部分组成:
metric
):指标名和描述当前样本特征的标签集合timestamp
):一个精确到毫秒的时间戳value
):一个 float64 的浮点型数据表示当前样本的值如下所示:
<--------------- metric ---------------------><-timestamp -><-value->
http_request_total{status="200", method="GET"}@1434417560938 => 94355
http_request_total{status="200", method="GET"}@1434417561287 => 94334http_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 name>{<label name> = <label value>, ...}
在 TSDB
内部,指标名称也只是一个特殊的标签,标签名为 __name__
,由于这个标签在 PromQL
中随时都会使用,所以在使用 PromQL
查询的时候就被单独写在了标签列表前面了。另外像 method=""
这样的空标签在 Prometheus
种相当于一个不存在的标签,在 Prometheus
代码里面是明确地剥离了空标签的,并不会存储它们。
每个不同的 metric_name
和 label
组合都称为时间序列,在 Prometheus
的表达式语言中,表达式或子表达式包括以下四种类型之一:
Instant vector
):一组时间序列,每个时间序列包含单个样本,它们共享相同的时间戳。也就是说,表达式的返回值中只会包含该时间序列中的最新的一个样本值。而相应的这样的表达式称之为瞬时向量表达式。Range vector
):一组时间序列,每个时间序列包含一段时间范围内的样本数据,这些是通过将时间选择器附加到方括号中的瞬时向量(例如 [5m]
5 分钟)而生成的。Scalar
):一个简单的数字浮点值。String
):一个简单的字符串值。所有这些指标都是 Prometheus
定期从 metrics
接口那里采集过来的。采集的间隔时间的设置由 prometheus.yml
配置中的 scrape_interval
指定。最多抓取间隔为 30
秒,这意味着至少每 30
秒就会有一个带有新时间戳记录的新数据点,这个值可能会更改,也可能不会更改,但是每隔 scrape_interval
都会产生一个新的数据点。
从存储上来讲所有的监控指标都是相同的,但是在不同的场景下这些指标又有一些细微的差异。 例如,在 Node Exporter
返回的样本中指标 node_load1
反应的是当前系统的负载状态,随着时间的变化这个指标返回的样本数据是在不断变化的。而指标 node_cpu_seconds_total
所获取到的样本数据却不同,它是一个持续增大的值,因为其反应的是 CPU
的累计使用时间,从理论上讲只要系统不关机,这个值是会一直变大。
为了能够帮助用户理解和区分这些不同监控指标之间的差异, Prometheus
定义了 4 种不同的指标类型: Counter(计数器)
、 Gauge(仪表盘)
、 Histogram(直方图)
、 Summary(摘要)
。
在 node_exporter
(后面会详细讲解)返回的样本数据中,其注释中也包含了该样本的类型为 counter
。例如:
# HELP node_cpu_seconds_total Seconds the CPUs spent in each mode.
# TYPE node_cpu_seconds_total **counter**
node_cpu_seconds_total{cpu="0",mode="idle"} 7.840379222e+07
node_cpu_seconds_total{cpu="0",mode="iowait"} 302235.3
Counter
(只增不减的计数器) 类型的指标其工作方式和计数器一样,是一个累计类型的数据指标,它代表单调递增 (只增不减
) 的计数器,**其值只能在重新启动时增加或重置为0。所以它对于存储诸如服务的 HTTP
请求数量或使用的 CPU
时间之类的信息非常有用。常见的监控指标,如 http_requests_total
、 node_cpu_seconds_total
都是 Counter
类型的监控指标。
不要使用计数器来显示可以减小的值。例如:不要使用
Counter
表示当前正在运行的进程数,而是使用Gauge
代替。
可能你会觉得一直增加的数据没什么用处,了解服务从开始有多少请求有什么价值吗?但是需要记住,每个指标都存储了时间戳的,所有你的 HTTP
请求数现在可能是1000万,但是 Prometheus
也会记录之前某个时间点的值,我们可以去查询过去一个小时内的请求数,当然更多的时候我们想要看到的是请求数增加或减少的速度有多快,因此通常情况对于 Counter
指标我们都是去查看变化率而不是本身的数字。 PromQL
内置的聚合操作和函数可以让用户对这些数据进行进一步的分析,例如,通过 rate()
函数获取 HTTP
请求的增长率:
rate(http_requests_total[5m])
Gauge
是可以任意上下波动数值的指标类型。 Gauge
通常用于测量值,例如温度或当前的内存使用量,还可用于可能上下波动的计数,例如请求并发数。
与 Counter
不同, Gauge
(可增可减的仪表盘)类型的指标侧重于反应系统的当前状态,因此这类指标的样本数据可增可减。常见指标如 node_memory_MemFree_bytes
(当前主机空闲的内存大小)、 node_memory_MemAvailable_bytes
(可用内存大小)都是 Gauge
类型的监控指标。由于 Gauge
指标仍然带有时间戳存储,所有我们可以看到随时间变化的值,通常可以直接把它们绘制出来,这样就可以看到值本身而不是变化率了,通过 Gauge
指标,用户可以直接查看系统的当前状态。
这些简单的指标类型都只是为每个样本获取一个数字,但 Prometheus
的强大之处在于如何让你跟踪它们,比如我们绘制了两张图,一个是 HTTP
请求的变化率,另一个是分配的 Gauge
类型的实际内存,直接从图上就可以看出这两个之间有一种关联性,当请求出现峰值的时候,内存的使用也会出现峰值,但是我们仔细观察也会发现在峰值过后,内存使用量并没有恢复到峰值前的水平,整体上它在逐渐增加,这表明很可能应用程序中存在内存泄露的问题,通过这些简单的指标就可以帮助我们找到这些可能存在的问题。
对于 Gauge
类型的监控指标,通过 PromQL
内置函数 delta()
可以获取样本在一段时间范围内的变化情况。例如,计算 CPU
温度在两个小时内的差异:
delta(cpu_temp_celsius{host="zeus"}[2h])
还可以直接使用 predict_linear()
对数据的变化趋势进行预测。例如,预测系统磁盘空间在 4 个小时之后的剩余情况:
predict_linear(node_filesystem_free_bytes[1h], 4 * 3600)
除了 Counter
和 Gauge
类型的监控指标以外, Prometheus
还定义了 Histogram
和 Summary
的指标类型。 Histogram
和 Summary
主用用于统计和分析样本的分布情况。
在大多数情况下人们都倾向于使用某些量化指标的平均值,例如CPU 的平均使用率、页面的平均响应时间,这种方式也有很明显的问题,以系统 API
调用的平均响应时间为例:如果大多数 API
请求都维持在 100ms
的响应时间范围内,而个别请求的响应时间需要 5s
,那么就会导致某些 WEB
页面的响应时间落到中位数上,而这种现象被称为长尾问题。
为了区分是平均的慢还是长尾的慢,最简单的方式就是按照请求延迟的范围进行分组。例如,统计延迟在 0~10ms
之间的请求数有多少而 10~20ms
之间的请求数又有多少。通过这种方式可以快速分析系统慢的原因。 Histogram
和 Summary
都是为了能够解决这样的问题存在的,通过 Histogram
和 Summary
类型的监控指标,我们可以快速了解监控样本的分布情况。
Summary
会采样观察结果(通常是请求持续时间和响应大小之类的数据)。它不仅提供了观测值的总数和所有观测值的总和,还可以计算滑动时间窗口内的可配置分位数。
基本数据指标名称为 <basename>
的 Summary
类型数据指标,在数据采集期间会显示多个时间序列:
<basename>{quantile=
"<φ>"}<basename>_sum
<basename>_count
φ (phi)是 Summary
类型中的参数,表示分位数。 φ 的取值范围是 0
到 1
之间,表示分位数的位置。
Summary
用于记录某些东西的 平均大小
,可能是计算所需的时间或处理的文件大小, Summary
显示两个相关的信息: count
(事件发生的次数)和 sum
(所有事件的总大小),如下图计算 Summary
指标可以返回次数为3和总和15,也就意味着3次计算总共需要15s来处理,平均每次计算需要花费5s。下一个样本的次数为10,总和为113,那么平均值为11.3,因为两组指标都记录有时间戳,所以我们可以使用 Summary
来构建一个图表,显示平均值的变化率,比如图上的语句表示的是5分钟时间段内的平均速率。
例如,指标 prometheus_tsdb_wal_fsync_duration_seconds
的指标类型为 Summary
,它记录了 Prometheus Server
中 wal_fsync
的处理时间,通过访问 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.012047795
prometheus_tsdb_wal_fsync_duration_seconds{quantile="0.9"} 0.012047795
prometheus_tsdb_wal_fsync_duration_seconds{quantile="0.99"} 0.012047795
prometheus_tsdb_wal_fsync_duration_seconds_sum 53.40982735799987
prometheus_tsdb_wal_fsync_duration_seconds_count 4563
从上面的样本中可以得知当前 Prometheus Server
进行 wal_fsync
操作的总次数为 4563 次,耗时 53.40982735799987s。其中中位数 (quantile=0.5
) 的耗时为 0.012047795s ,9 分位数 (quantile=0.9
) 的耗时为 0.012047795s。
Histogram
对观测值(通常是请求持续时间或响应大小之类的数据)进行采样,并将其计数在可配置的数值区间中。它也提供了所有数据的总和。
基本数据指标名称为 <basename>
的 Histogram
数据指标,在数据采集期间会显示多个时间序列:
<basename>_bucket{le="<数值区间的上边界>"}
<basename>_sum
<basename>_count
(与上述 <basename>_bucket{le="+Inf"}
相同)尽管 Summary
非常有用,但是平均值会隐藏一些细节,上图中10与113的总和包含非常广的范围,如果我们想查看时间花在什么地方了,那么我们就需要直方图了。直方图以 bucket
桶的形式记录数据,所以我们可能有一个桶用于需要1s或更少的计算,另一个桶用于5s或更少、10s或更少、20s或更少、60s或更少。该指标返回每个存储桶的计数,其中3个在5s或更短的时间内完成,6个在10s或更短的时间内完成。 Prometheus
中的直方图是累积的,因此所有10次计算都属于60s或更少的时间段,而在这10次中,有9次的处理时间为20s或更少,这显示了数据的分布。所以可以看到我们的大部分计算都在10s以下,只有一个超过20s,这对于计算百分位数很有用。如下所示:
在 Prometheus Server
自身返回的样本数据中,我们也能找到类型为 Histogram
的监控指标
# HELP prometheus_tsdb_compaction_chunk_range_seconds Final time range of chunks on their first compaction
# TYPE prometheus_tsdb_compaction_chunk_range_seconds histogram
prometheus_tsdb_compaction_chunk_range_seconds_bucket{le="100"} 28240
prometheus_tsdb_compaction_chunk_range_seconds_bucket{le="400"} 28240
prometheus_tsdb_compaction_chunk_range_seconds_bucket{le="1600"} 28240
prometheus_tsdb_compaction_chunk_range_seconds_bucket{le="6400"} 28240
prometheus_tsdb_compaction_chunk_range_seconds_bucket{le="25600"} 317534
prometheus_tsdb_compaction_chunk_range_seconds_bucket{le="102400"} 3.380749e+06
prometheus_tsdb_compaction_chunk_range_seconds_bucket{le="409600"} 3.559075e+06
prometheus_tsdb_compaction_chunk_range_seconds_bucket{le="1.6384e+06"} 4.128665e+06
prometheus_tsdb_compaction_chunk_range_seconds_bucket{le="6.5536e+06"} 8.19135613e+08
prometheus_tsdb_compaction_chunk_range_seconds_bucket{le="2.62144e+07"} 8.62465566e+08
prometheus_tsdb_compaction_chunk_range_seconds_bucket{le="+Inf"} 8.62465566e+08
prometheus_tsdb_compaction_chunk_range_seconds_sum 1.778631422788386e+15
prometheus_tsdb_compaction_chunk_range_seconds_count 8.62465566e+08
与 Summary
类型的指标相似之处在于 Histogram
类型的样本同样会反应当前指标的记录的总数(以 _count
作为后缀)以及其值的总量(以 _sum
作为后缀)。不同在于 Histogram
指标直接反应了在不同区间内样本的个数,区间通过标签 le
进行定义。
参考来源
https://prometheus.io/docs/introduction/overview/
https://github.com/cnych/qikqiak.com
好了,亲爱的读者们,如果你觉得 Prometheus
监控就像一位超级英雄,而 PromQL
查询就是他的神奇武器,那么请记住:在监控的世界里, PromQL
就是我们的万能钥匙,让我们能够打败任何可疑的故障怪兽!