prometheus学习笔记

437 阅读4分钟

一  架构

- Prometheus Server是Prometheus组件中的核心部分

- Retrieval是负责定时去暴露的目标页面上去抓取采样指标数据

- Storage是负责将采样数据写磁盘

- PromQL是Prometheus提供的查询语言模块

- Exporter将监控数据采集的端点通过HTTP服务的形式暴露给Prometheus Server,Prometheus Server通过访问该Exporter提供的Endpoint端点,即可获取到需要采集的监控数据

- 在AlertManager中我们可以与邮件,Slack等等内置的通知方式进行集成,也可以通过Webhook自定义告警处理方式

- PushGateway将内部网络的监控数据主动Push到Gateway当中。而Prometheus Server则可以采用同样Pull的方式从PushGateway中获取到监控数据

数据

每个Metric name代表了一类的指标,他们可以携带不同的Labels,每个Metric name + Label组合成代表了一条时间序列的数据。

存储架构

使用了LevelDB的引擎,它的特点是顺序读写性能非常高,为了得到顺序的时间序列哈希索引值,Prometheus使用了FNV,FNV能快速hash大量数据并保持较小的冲突率,它的高度分散使它适用于hash一些非常相近的字符串,比如URL,hostname,文件名,text,IP地址等。

文件架构

根目录下,顺序排列着编了号的 blocks,每个 block 中包含 index 和 chunk 文件夹,后者里面包含编了号的 chunks,每个 chunk 包含许多不同时序的样本数据。

其中 index 文件中的信息可以帮我我们快速锁定时序的标签及其可能的取值,进而找到相关的时序和持有该时序样本数据的 chunks。

值得注意的是,最新的 block 文件夹中还包含一个 wal 文件夹,后者将承担故障恢复的职责。

当查询某个时间范围内的数据,我们可以直接忽略在时间范围外的 blocks,需要将查询发送到不同的 block 中,再将结果聚合。

删除超过留存时间的数据变得异常简单,直接删除整个文件夹即可

二 数据存储过程

步骤1

当samples传入时,它先记录在磁盘上的预写日志(WAL)中以确保持久性(这意味着即使机器突然崩溃,我们也可以从中恢复内存中的数据)

三种 record:

Series record 由写入请求中所有series的标签值组成,

Samples record包含相应Series的引用和写入请求中属于该Series的samples列表

Tombstones record包含已删除的Series以及要删除的时间范围 ,当收到删除请求时,我们不会立即将其从内存中删除,我们存储了一个叫做“Tombstones”的东西。

Series record只写一次,如果写入请求包含新Series,则Series record始终写入Samples record之前,否则在重放期间,如果Samples record放置在Samples record之前,则record中的Series引用将不会指向任何Series。

WAL

数据库中发生的事件的顺序日志。在写入/修改/删除数据库中的数据之前,首先将事件记录到 WAL 中,然后在数据库中执行必要的操作。

步骤二

samples存储在称为“chunk”的压缩单元中,在内存里,它是我们唯一可以主动写入数据的单元,

步骤三

一旦Chunk写满,会切割出新的chunk,完整的chunk就会刷新到磁盘并从磁盘进行内存映射,同时只在内存中存储一 个引用。通过内存映射,我们可以在需要时使用该引用动态地将chunk加载到内存中;这是操作系统提供的功能mmap。

chunks_head:

    - magic数字将此文件标识为块文件。

    - version告诉我们如何解析这个文件。

    - padding用于任何未来的标题

    - 多个chunk.以下是单个chunk:

    - series ref: 用于访问该series在内存中的series ID,前4个字节告诉chunk所在的文件号,后4个字节告诉chunk开始的文件中的偏移量(即 的第一个字节series ref)。如果块在文件中00093并且在文件series ref中的字节偏移处开始1234,则该块的引用将是(93 << 32) | 1234(左移位然后按位或)。

    - mint

    - maxt

    - len

    - encoding

    - data

    - CRC32

步骤四

当 Head 中的数据 span 时chunkRange*3/2,第一个chunkRange数据(这里是 2h)被压缩成一个持久块。此时 WAL 被截断并创建了一个“checkponit”

由于写请求可以是随机的,因此在不遍历所有记录的情况下确定WAL段中样本的时间范围既不容易也不高效。因此,我们删除了片段的前2/3。

但是series记录仅写入一次,因此,如果盲目删除WAL段,则会丢失这些记录,因此无法在启动时恢复这些series。另外,在前2/3段中可能还没有从头上截断一些sample,因此您也会丢失它们。

截断WAL之前,我们从那些要删除的WAL段中创建一个“checkpoint”。您可以将checkpoint视为经过过滤的WAL。checkpoint将按顺序遍历所有要截断的记录:

  • 删除不再在Head中的series的所有series记录。

  • 删除时间T之前的所有sample。

  • 删除T之前时间范围内的所有逻辑删除记录。

  • 保留与在WAL中找到的series,sample和Tombstones ≤记录相同的方式(以它们在WAL中出现的顺序)。

您可能会想为什么在删除WAL段之前,为什么我们需要在检查点中跟踪段号。

关键是,创建检查点和删除WAL段不是原子的。在两者之间可能发生任何事情,并防止WAL段删除。因此,我们将不得不重播将被删除的WAL片段的另外2/3,从而使重播速度变慢。

block持久块

meta.json (file): 块的元数据。

chunks (目录):包含原始块,没有关于块的任何元数据。

chunks文件:

    - magic数字将此文件标识为块文件。

    - version告诉我们如何解析这个文件。

    - padding用于任何未来的标题

    - 多个chunk.以下是单个chunk,跟head有区别:

    - len

    - encoding

    - data

    - CRC32

index (file): 这个块的索引。使用了倒排索引

tombstones (file): 删除标记以在查询块时排除样本。

三 发生故障时,重启流程

在启动时,首先我们遍历chunks_head目录中的所有chunks,并在内存中构建series ref(chunk引用列表以及属于该series引用的mint和maxt的映射)。

当我们遇到“series”记录时,在创建series之后,我们在上图中查找该series的引用,并且如果存在任何内存映射chunks,则将该列表附加到该series中。

当我们遇到“sample”记录时,如果该sample的相应chunks具有任何内存映射的chunk,并且该sample属于其涵盖的时间范围,那么我们将跳过该sample。如果不是,那么我们将sample提取到Head块中。

其中重播每个单独的sample以重新创建压缩块。现在,我们已经在磁盘上压缩了完整的chunks,我们不需要重新创建这些chunks,而仍然需要从WAL创建不完整的chunks。现在,使用磁盘中的这些内存映射chunks,可以按以下方式进行重播。

我们首先从最后一个检查点开始依次遍历记录(与它关联的最大数字的检查点是最后一个检查点)。对于checkpoint.X,X告诉我们需要从哪个WAL段继续重放,即X + 1。因此,在上面的示例中,在重播checkpoint.000003之后,我们从WAL段000004继续重播。

当大量写入请求时,您要避免随机写入磁盘,跟避免写入放大。此外,在读取记录时,您要确保它没有损坏(在突然关闭或磁盘故障时很容易发生)。

Prometheus具有WAL的实现,其中一条记录只是一个字节切片,而调用者必须负责对记录进行编码。

为了解决以上两个问题,WAL软件包执行以下操作:

- 数据一次一页地写入磁盘。一页长32KiB。如果记录大于32KiB,则将其分解成较小的块,每块接收一个WAL记录头,以便进行簿记,以了解该块是记录的结尾,开始还是在中间。

- 记录的校验和将附加在末尾,以检测读取时的任何损坏。

四 读取数据流程

Select([]matcher)

1.获取每一个单独matcher匹配的series

2.将所有matcher的series一起处理,得到所有匹配的series ID

(series ID,可以知道series信息在索引中的位置)

job="bar.*", status!"5.*"

-> (job="bar.*") ∩ (status!"5.*")

-> (job="bar.*") - (status="5.*")

-> ((job="bar1") ∪ (job="bar2")) - (status="501")

-> ((s3) ∪ (s4)) - (s2, s4)

-> (s3, s4) - (s2, s4)->(s3)

3.在series表,通过series id得到series

4.通过查询器,匹配所有mint到maxt范围内的chunk引用

5.创建一个iterator,遍历所有chunk,获取mint到maxt范围内的sample

Index

索引包含查询此块数据所需的所有内容。它不与任何其他块或外部实体共享任何数据,这使得可以在没有任何依赖性的情况下读取/查询块。

索引是一种“倒排索引”

Inverted index 指的是将单词或记录作为索引,将文档ID作为记录,这样便可以方便地通过单词或记录查找到其所在的文档。

Series ID -> document ID, document ID -> posting, series ID -> posting.

参考资料: ganeshvernekar.com/blog/