浅谈Prometheus的数据存储

本文是结合耗子叔的视频及Prometheus作者部分原文整理,加上部分个人理解而来,膜拜大神~
概述
Prometheus是一套开源的监控&报警&时间序列数据库的组合
Prometheus内部主要分为三大块,Retrieval是负责定时去暴露的目标页面上去抓取采样指标数据,Storage是负责将采样数据写磁盘,PromQL是Prometheus提供的查询语言模块
其有着非常高效的时间序列数据存储方法,每个采样数据仅仅占用3.5byte左右空间
在早期有一个单独的项目叫做 TSDB,但是,在2.1.x的某个版本,已经不单独维护这个项目了,直接将这个项目合并到了prometheus的主干上了
prometheus每次抓取的数据,对于操作者来说可见的格式(即在prometheus界面查询到的值)
requests_total{path="/status", method="GET", instance="10.0.0.1:80"} @1534317560938 94355
意思就是在1534317560938这个时 间点,10.0.0.1:80这个实例上,GET /status 这个请求的次数累计是 94355次
最终存储在TSDB中的格式为
{__name__="requests_total", path="/status", method="GET", instance="10.0.0.1:80"}
时间序列
Data scheme数据标识
identifier -> (t0, v0), (t1, v1), (t2, v2), (t3, v3), ...
Prometheus Data Model数据模型
<metric name>{<label name>=<label value>, ...}
Typical set of series identifiers

- Query 查询
__name__="requests_total":查询所有属于requests_total的序列
method="PUT|POST":查询所有序列中方法是PUT或POST的序列
二维模型
-
Write写:每个目标暴露成百上千个不同的时间序列,写入模式是完全垂直和高度并发的,因为来自每个目标的样本是独立的
-
Query查:查询数据时可以并行和批处理
series
^
│ . . . . . . . . . . . . . . . . . . . . . . {__name__="request_total", method="GET"}
│ . . . . . . . . . . . . . . . . . . . . . . {__name__="request_total", method="POST"}
│ . . . . . . .
│ . . . . . . . . . . . . . . . . . . . ...
│ . . . . . . . . . . . . . . . . . . . . .
│ . . . . . . . . . . . . . . . . . . . . . {__name__="errors_total", method="POST"}
│ . . . . . . . . . . . . . . . . . {__name__="errors_total", method="GET"}
│ . . . . . . . . . . . . . .
│ . . . . . . . . . . . . . . . . . . . ...
│ . . . . . . . . . . . . . . . . . . . .
v
<-------------------- time --------------------->
二维模型中横轴表示时间,纵轴表示各数据点
这类设计会带来的问题如下
存储问题

如上图所示,在二维模型中的读写差别是很大的
(时间序列查询)读时带来的随机读问题和查询带来的随机写问题,(查询)读往往会比写更复杂,这是很慢的。尽管用了SSD,但会带来写放大的问题,SSD是4k写,256k删除,SSD之所以快,实际上靠的是算法,因此在文件碎片如此大的情况下,都是不能满足的

理想状态下的写应该是顺序写、批量写,对于相同的时间序列读应该也是顺序读
存储策略的演进
1.x版本
1.x版本下,存储情况是这样的
- 每个时间序列都对应一个文件
- 在内存中批量处理1kb的的chunk
┌──────────┬─────────┬─────────┬─────────┬─────────┐ series A
└──────────┴─────────┴─────────┴─────────┴─────────┘
┌──────────┬─────────┬─────────┬─────────┬─────────┐ series B
└──────────┴─────────┴─────────┴─────────┴─────────┘
. . .
┌──────────┬─────────┬─────────┬─────────┬─────────┬─────────┐ series XYZ
└──────────┴─────────┴─────────┴─────────┴─────────┴─────────┘
chunk 1 chunk 2 chunk 3 ...
存在的问题:
-
chunk保存在内存中,如果应用程序或节点崩溃,它可能会丢失 -
由于时间序列的维度很多,对于的文件个数也会很多,这可能耗尽操作系统的
inode -
上千的
chunk保存在硬盘需要持久化,可能会导致磁盘I/O非常繁忙 -
磁盘
I/O打开很多的文件,会导致非常高的延迟 -
旧数据需要清理,这可能会导致
SSD的写放大 -
非常大的
CPU