Prometheus不仅是作为监控用的应用系统,还自带时序数据库。Prometheus存储从借助第三方数据库LevelDB
到自研时序数据库,经历过多个版本的迭代:V1.0(2012)、V2.0(2015)、V3.0(2017)。本章将主要围绕Prometheus 3.0版本存储的原理,即Prometheus 2.0+的Prometheus TSDB的本地存储,从存储文件的格式、存储的原理、chunk、索引、block、WAL日志、tombstones、Checkpoint等相关知识点展开介绍。
存储文件结构
以kube-prometheus部署形态为例,Prometheus pod本地PV存储文件位于$Volume_PATH/prometheus-db目录,文件结构如下所示。
├── 01HB97EB5NDVB1VFB04W5D1XGE
│ ├── chunks
│ ├── index
│ ├── meta.json
│ └── tombstones
├── 01HB9N4P1F4GDPYPFZ1XDTM87S
│ ├── chunks
│ ├── index
│ ├── meta.json
│ └── tombstones
├── chunks_head
│ ├── 000294
│ ├── 000295
...
├── queries.active
└── wal
├── 00001376
├── 00001377
...
└── checkpoint.00001375
如下图所示,Head块位于内存,而灰色Block块是磁盘上的持久块。为了防止数据丢失,会先构建持久写的预写日志(Write-Ahead-Log, WAL)。传入的样本(紫色框),首先进入Head块并在内存中停留一段时间,然后刷新到磁盘并进行内存映射(M-map)。当这些内存映射块或内存中的块老化到一定程度时,它们将作为持久块刷新到磁盘Block。当多个块变老时,它们将被合并,并在超过保留期后最终被删除。
存储结构示意图
Head块
Head块对应存储目录中的chunks_head子目录。当一个样本传入时,它会被加载到Head中的active chunk,这是唯一可以主动写入数据的单元,为了防止内存数据丢失还会做一次预写日志 (WAL)。 一旦active chunk被填满时(超过2小时或120样本),将旧的数据截断为head_chunk。head_chunk被刷新到磁盘然后进行内存映射。active chunk继续写入数据、截断数据、写入到内存映射,如此反复。
WAL文件
与大多数数据库存储类似,prometheus也采用预写日志"WAL(Write-Ahead Log)"的机制来持久化数据。WAL文件是Prometheus的重要组成部分,它用于持久化数据,并确保在系统故障或崩溃情况下数据不会丢失。WAL采用日志追加模式,即数据会先被追加到WAL文件中,然后再被刷新到存储引擎中。
WAL文件包含了Prometheus收集到的时间序列数据,它们是以二进制的格式存储的,WAL文件记录了Prometheus的写操作,例如指标数据的采集和更新。当Prometheus重启或崩溃时,它可以通过回放WAL文件来恢复未保存的数据。WAL文件支持高效的写入操作、节省磁盘空间和降低系统故障引起的数据丢失风险,还可以通过配置WAL文件的滚动策略来控制文件的大小和保留时间。
Checkpoint文件
在Prometheus的WAL(Write-Ahead Log)目录中,有一个名为checkpoint
的文件,它起着重要的作用。checkpoint文件用于记录WAL日志文件的写入进度,以及在重启或崩溃后用于恢复数据的位置。
WAL目录中的checkpoint文件保留了一个偏移量,指示Prometheus最后一次成功写入WAL文件的位置。当Prometheus启动时,它会首先读取checkpoint文件,以确定该从哪个WAL日志文件恢复数据。在正常运行期间,Prometheus会定期更新checkpoint文件,以确保写入进度得到及时记录。
当Prometheus重启或崩溃时,它会根据checkpoint文件中记录的偏移量来寻找最后一次成功写入的WAL文件,并从该位置开始恢复数据。通过使用checkpoint文件,Prometheus能够可靠地保持数据的一致性和完整性,避免数据丢失或损坏。这样,即使Prometheus异常停止或在恢复过程中崩溃,它也可以通过读取checkpoint文件来找到最后一次写入的位置,并从该位置开始回放WAL日志文件以恢复数据。
Block块
Prometheus中以每2个小时为一个时间窗口,即将2小时内产生的数据存储在一个block中,监控数据会以时间段的形式被拆分成不同的block,因此这样的block会有很多。
block会压缩、合并成历史数据块、随着压缩合并会减少block的个数,这与LSM树的机制类似。压缩过程中主要完成3项工作,分别是定期执行压缩、合并小block到大block以及清理过期block。
block大小并不固定,一般按照设定的步长成倍数递增。。默认最小的block保存2小时的监控数据,如果步长为4,那么block对应的时间依次为:2h、8h、24h、72h,其格式如下所示。
│ └── 01HB97EB5NDVB1VFB04W5D1XGE # block块
│ │ ├── chunks # 样本数据
│ │ │ └── 000001
│ │ ├── index # 索引文件
│ │ ├── meta.json # block元数据信息
│ │ └── tombstones # 逻辑数据
每个block都拥有全局唯一的UUID
,即全局字典ID。UUID
总长度为128位(16字节),前48位(6字节)是时间戳,后80位是(10字节)为随机数。Prometheus通过base32
算法将16字节的UUID转换为26字节的可排序字符串,比如01G2BP4KNB85XK6W8P5Y2GJRTA
的前10字节就是由UUID的前6个字节的时间戳转换而来的。通过这样的命名方式,可以通过block文件名确认block的创建时间,这对连续block的排序、查询都有着非常重要的作用。
每个block都有自己单独的目录,里面包含该时间窗口内所有的chunk、Index、tombstones、meta.json。
chunks
:会有一个或多个chunk,用于保存时序数据。每个chunk最大为512MB,超过部分则会被截断为多个chunk保存,通过数学编号来命名。Index
:索引文件,它是Prometheus TSDB实现高效查询的基础,它可以通过metrics name和labels查找s时序数据在chunk文件中的位置。索引文件会将指标名和标签索引到样本的时间序列中。tombstones
:用于对数据进行软删除。Prometheus TSDB采用了“标记删除”的策略来降低删除操作的成本:如果通过API删除时间序列,删除记录会保存在单独的逻辑文件tombstones中;读取时序数据时,也会根据tombstones文件中的删除记录来过滤已删除的部分。meta.json
:block的元数据信息,这些元数据的信息对block的合并、删除等非常有帮助。