
2.4 数据存储
Prometheus 的数据存储分为本地存储和远程存储两种方式,下面针对这两种方式进行详细讲解。
2.4.1 本地存储
Prometheus的本地存储经历了两个版本的重要演进,从 Prometheus V1版本升级到V2版本,又升级为现在的V3版本。从V2版本开始,Prometheus的本地存储便充分借鉴了Facebook Gorilla的设计思想及Facebook内部的长期经验,总结了时序数据的以下特点:
◎ 相邻数据点时间戳的差值相对固定,即使有变化,也仅在一个很小的范围内浮动;
◎ 相邻数据点的值的变化幅度很小,甚至无变化;
◎ 对热数据的查询频率远远超出对非热点数据的查询频率,并且数据距离现在越近,热度越高。
在 Facebook 上发表的关于 Gorilla 的文章(http://www.vldb.org/pvldb/vol8/p1816-teller.pdf)中提出了一种压缩算法,可以将16字节的样本数据压缩到1.37字节,并且通过块(chunk)保存,如图2-8所示。

图2-8
其原理非常简单,首先是对时间戳采用两次去差值的方式,加入 t0=10:00:00、t1=10:01:02和 t2=10:02:02三个时间点。在第1次压缩时,t1−t0=62,t2−t1=60;在第2次压缩时,60−62=−2。采用保存差值的方式来大幅度降低存储空间,采样的频率通常也是固定的,这样的差值几乎都是 0。对于指标值,则对相邻指标进行异或(XOR)操作,如果前后两个采样值相等,二者异或的结果为0,则只需保存一个0,而且监控数据通常都变化不大,异或的结果将会很小,便于保存。
但Prometheus V2的存储设计存在很大的问题,如下所述。
(1)每个序列都有一个文件,这样会耗尽文件系统的Inode,并且同时随机读写了这么多的文件,磁盘的性能会大大降低,对于SSD还会存在写放大的问题。
(2)被监控对象不停更新。例如,在Kubernetes容器监控方面,容器的生命周期比较短暂,指标不停变化,会形成“时序流失”。随着时间的推移,虽然有LevelDB负责时序的索引,但这些时间序列会形成线性增长,导致内存积压过多,如图2-9所示。

图2-9
(3)无法预测容量。数据首先在内存中缓慢构建,在被保存到磁盘中后,内存中的数据会被回收,这样发生时序流失时,内存的消耗就会增加。如果要查询已经存储到磁盘的历史数据,则会再次将相关数据全部加载到内存中,此时很容易发生内存溢出(OOM),导致Prometheus被“杀掉”,给运维人员造成很大的压力。
经过一些系列的参数调整后,Prometheus 内存溢出的问题仍然没有得到解决。在2017年,Prometheus的存储迎来了 V3版本。Prometheus V3保留了高压缩比的块保存,将多个块保存到一个文件中,并通过一个 index 文件去索引,从而避免产生大量的文件;并且为了防止数据丢失,引入了WAL。
如图2-10所示,Prometheus V3将整个存储空间按照时间水平拆分成很多独立区间,每个区间就相当于一个独立的数据库。

图2-10
V3版本的优点如下:
◎ 从 V2版本的每个时序一个文件改进到 V3版本的一段时间一个文件,可以有效避免时序流失的问题,还可以任意组织多个块的数据到一个文件中;
◎ 每个文件块最大支持512MB,可避免SSD写放大;
◎ 在删除数据的时候更简单,直接删除分区即可;
◎ 在查询历史数据时,由于已经按照时间排序,所以可以将内存数据和此磁盘数据合并,通过懒加载的方式载入数据,而不需要将所有磁盘数据加载内存,避免发生OOM。
如图2-11所示为V3版本的存储结构。其中wal目录记录的是监控数据的WAL;每个block是一个目录,该目录下的chunks用来保存具体的监控数据,meta.json用来记录元数据,index则记录索引。具体内容会在有关存储的章节中详细介绍。

图2-11
2.4.2 远程存储
为了提高对大量历史数据持久化存储的能力,Prometheus 在1.6版本后支持远程存储,Adapter需要实现Prometheus的read接口和write接口,并且将read和write转化为每种数据库各自的协议,如图2-12所示。

图2-12
在用户查询数据时,Prometheus会通过配置的查询接口发送 HTTP请求,查询开始时间、结束时间及指标属性等,Adapter会返回相应的时序数据;相应地,在用户写入数据时,HTTP请求Adapter的消息体会包含时序数组(样本数据)。