理解时间序列 - prometheus-bookhttps://yunlzheng.gitbook.io/prometheus-book/parti-prometheus-ji-chu/promql/what-is-prometheus-metrics-and-labelsPrometheus 采集的监控数据都是以指标(metric)的形式存储在内置的 TSDB 数据库中,这些数据都是时间序列:一个带时间戳的数据,这些数据具有一个标识符和一组样本值。除了存储的时间序列,Prometheus 还可以根据查询请求产生临时的、衍生的时间序列作为返回结果。
时间序列(指标名称+标签 = 唯一一条时间序列)
通过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 会将所有采集到的样本数据以时间序列的形式保存在内存数据库中,并定时刷新到硬盘上,时间序列是按照时间戳和值的序列方式存放的,我们可以称之为向量(vector),每一条时间序列都由一个指标名称和一组标签(键值对)来唯一标识。
- 指标名称反映了被监控样本的含义(如
http_request_total
表示的是对应服务器处理的 HTTP 请求总数)。 - 标签可以用来区分不同的维度(比如
method="GET"
与method="POST"
就可以用来区分这两种不同的 HTTP 请求指标数据)。
如下所示,可以将时间序列理解为一个以时间为 Y 轴的数字矩阵:
^│ . . . . . . . . . . . . . . . . . . . http_request_total{method="GET",status="200"}│ . . . . . . . . . . . . . . . . . . . http_request_total{method="POST",status="500"}│ . . . . . . . . . . . . . . . . . .│ . . . . . . . . . . . . . . . . . .v<------------------ 时间 ---------------->
需要注意的是指标名称只能由 ASCII 字符、数字、下划线以及冒号组成,同时必须匹配正则表达式 [a-zA-Z_:][a-zA-Z0-9_:]*
(冒号不能用来定义指标名称,是用来表示用户自定义的记录规则)。标签的名称只能由 ASCII 字符、数字以及下划线组成并满足正则表达式 [a-zA-Z_][a-zA-Z0-9_]*
,其中以 __
作为前缀的标签,是系统保留的关键字,只能在系统内部使用,标签的值则可以包含任何 Unicode 编码的字符。
样本(指标名称+时间戳+样本值)
Prometheus会将所有采集到的样本数据以时间序列(time-series)的方式保存在内存数据库中,
<--------------- metric ---------------------><-timestamp -><-value->
http_request_total{status="200", method="GET"}@1434417560938 => 94355
并且定时保存到硬盘上。time-series是按照时间戳和值的序列顺序存放的,我们称之为向量(vector). 每条time-series通过指标名称(metrics name)和一组标签集(labelset)命名。如下所示,可以将time-series理解为一个以时间为Y轴的数字矩阵:
^│ . . . . . . . . . . . . . . . . . node_cpu{cpu="cpu0",mode="idle"} 时间序列1│ . . . . . . . . . . . . . . . . . node_cpu{cpu="cpu0",mode="system"} 时间序列2│ . . . . . . . . . . . . . . . .. node_load1{} 时间序列3 v<------------------ 时间 [5m] 每个点都是对应时间序列 n/1/2/3 的一个样本 ---------------->
在time-series中的每一个点称为一个样本(sample),样本由以下三部分组成:
-
指标(metric):metric name和描述当前样本特征的labelsets;
-
时间戳(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 => 38544http_request_total{status="200", method="POST"}@1434417560938 => 4748
http_request_total{status="200", method="POST"}@1434417561287 => 4785
指标(Metric)(端点暴露出来的数据 指标名称+标签+样本值)
想要暴露 Prometheus 指标服务只需要暴露一个 HTTP 端点,并提供 Prometheus 基于文本格式的指标数据即可。这种指标格式是非常友好的,基本上的格式看起来类似于下面的这段代码:
# HELP http_requests_total The total number of processed HTTP requests.
# TYPE http_requests_total counter
http_requests_total{status="200"} 8556
http_requests_total{status="404"} 20
http_requests_total{status="500"} 68
其中 #
开头的行是注释信息,用来描述下面提供的指标含义,其他未注释行代表一个样本(带有指标名、标签和样本值),使其非常容易从系统和服务中暴露指标出来。
事实上所有的指标也都是通过如下所示的格式来标识的:
<metric name>{<label name>=<label value>, ...}
例如,指标名称是 http_request_total
,标签集为 method="POST", endpoint="/messages"
,那么我们可以用下面的方式来标识这个指标:
http_request_total{method="POST", endpoint="/messages"}
而事实上 Prometheus 的底层实现中指标名称实际上是以 __name__=<metric name>
的形式保存在数据库中的,所以上面的指标也等同与下面的指标:
{__name__="http_request_total", method="POST", endpoint="/messages"}
所以也可以认为一个指标就是一个标签集,只是这个标签集里面一定包含一个 __name__
的标签来定义这个指标的名称。
--------------------------------------------------------------------------------------------------------------------------
在形式上,所有的指标(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 LabelSettype LabelSet map[LabelName]LabelValuetype LabelName stringtype LabelValue string
存储格式
Prometheus 按照两个小时为一个时间窗口,将两小时内产生的数据存储在一个块(Block)中,每个块都是一个单独的目录,里面包含该时间窗口内的所有样本数据(chunks),元数据文件(meta.json)以及索引文件(index)。
其中索引文件会将指标名称和标签索引到样本数据的时间序列中,如果该期间通过 API 删除时间序列,删除记录会保存在单独的逻辑文件 tombstone 当中。
当前样本数据所在的块会被直接保存在内存数据库中,不会持久化到磁盘中,为了确保 Prometheus 发生崩溃或重启时能够恢复数据,Prometheus 启动时会通过预写日志(write-ahead-log(WAL))来重新播放记录,从而恢复数据,预写日志文件保存在 wal 目录中,wal 文件包括还没有被压缩的原始数据,所以比常规的块文件大得多。
Prometheus 保存块数据的目录结构如下所示:
.
├── 01FB9HHY61KAN6BRDYPTXDX9YF
│ ├── chunks
│ │ └── 000001
│ ├── index
│ ├── meta.json
│ └── tombstones
├── 01FB9Q76Z0J10WJZX3PYQYJ96R
│ ├── chunks
│ │ └── 000001
│ ├── index
│ ├── meta.json
│ └── tombstones
├── chunks_head
│ ├── 000014
│ └── 000015
├── lock
├── queries.active
└── wal├── 00000011├── 00000012├── 00000013├── 00000014└── checkpoint.00000010└── 000000007 directories, 17 files