容器监控实践—Prometheus存储机制

概述

Prometheus提供了本地存储,即tsdb时序数据库,本地存储给Prometheus带来了简单高效的使用体验,prometheus2.0之后压缩数据能力也获得了很大的提高。能够在单节点的状况下知足大部分用户的监控需求。html

但本地存储也限制了Prometheus的可扩展性,带来了数据持久化等一系列的问题。为了解决单节点存储的限制,prometheus没有本身实现集群存储,而是提供了远程读写的接口,让用户本身选择合适的时序数据库来实现prometheus的扩展性。linux

Prometheus 1.x版本的TSDB(V2存储引擎)基于LevelDB,而且使用了和Facebook Gorilla同样的压缩算法,可以将16个字节的数据点压缩到平均1.37个字节。git

Prometheus 2.x版本引入了全新的V3存储引擎,提供了更高的写入和查询性能算法

如下全部内容均基于prometheus2.7版本sql

本地存储

存储原理

Prometheus按2小时一个block进行存储,每一个block由一个目录组成,该目录里包含:一个或者多个chunk文件(保存timeseries数据)、一个metadata文件、一个index文件(经过metric name和labels查找timeseries数据在chunk文件的位置)。数据库

最新写入的数据保存在内存block中,达到2小时后写入磁盘。为了防止程序崩溃致使数据丢失,实现了WAL(write-ahead-log)机制,启动时会以写入日志(WAL)的方式来实现重播,从而恢复数据。json

删除数据时,删除条目会记录在独立的tombstone文件中,而不是当即从chunk文件删除。缓存

经过时间窗口的形式保存全部的样本数据,能够明显提升Prometheus的查询效率,当查询一段时间范围内的全部样本数据时,只须要简单的从落在该范围内的块中查询数据便可。bash

这些2小时的block会在后台压缩成更大的block,数据压缩合并成更高level的block文件后删除低level的block文件。这个和leveldb、rocksdb等LSM树的思路一致。服务器

这些设计和Gorilla的设计高度类似,因此Prometheus几乎就是等于一个缓存TSDB。它本地存储的特色决定了它不能用于long-term数据存储,只能用于短时间窗口的timeseries数据保存和查询,而且不具备高可用性(宕机会致使历史数据没法读取)。

内存中的block数据未写入磁盘时,block目录下面主要保存wal文件:

./data/01BKGV7JBM69T2G1BGBGM6KB12
./data/01BKGV7JBM69T2G1BGBGM6KB12/meta.json
./data/01BKGV7JBM69T2G1BGBGM6KB12/wal/000002
./data/01BKGV7JBM69T2G1BGBGM6KB12/wal/000001

持久化的block目录下wal文件被删除,timeseries数据保存在chunk文件里。index用于索引timeseries在wal文件里的位置。

./data/01BKGV7JC0RY8A6MACW02A2PJD
./data/01BKGV7JC0RY8A6MACW02A2PJD/meta.json
./data/01BKGV7JC0RY8A6MACW02A2PJD/index
./data/01BKGV7JC0RY8A6MACW02A2PJD/chunks
./data/01BKGV7JC0RY8A6MACW02A2PJD/chunks/000001
./data/01BKGV7JC0RY8A6MACW02A2PJD/tombstones

存储配置

对于本地存储,prometheus提供了一些配置项,主要包括:

  • --storage.tsdb.path: 存储数据的目录,默认为data/,若是要挂外部存储,能够指定该目录
  • --storage.tsdb.retention.time: 数据过时清理时间,默认保存15天
  • --storage.tsdb.retention.size: 实验性质,声明数据块的最大值,不包括wal文件,如512MB
  • --storage.tsdb.retention: 已被废弃,改成使用storage.tsdb.retention.time

Prometheus将全部当前使用的块保留在内存中。此外,它将最新使用的块保留在内存中,最大内存能够经过storage.local.memory-chunks标志配置。

监测当前使用的内存量:

  • prometheus_local_storage_memory_chunks
  • process_resident_memory_bytes

监测当前使用的存储指标:

  • prometheus_local_storage_memory_series: 时间序列持有的内存当前块数量
  • prometheus_local_storage_memory_chunks: 在内存中持久块的当前数量
  • prometheus_local_storage_chunks_to_persist: 当前仍然须要持久化到磁盘的的内存块数量
  • prometheus_local_storage_persistence_urgency_score: 紧急程度分数

prometheus 2.0的存储升级

prometheus 2.0于2017-11-08发布,主要是存储引擎进行了优化。

性能的总体提升:

  • 与 Prometheus 1.8 相比,CPU使用率下降了 20% - 40%
  • 与 Prometheus 1.8 相比,磁盘空间使用率下降了 33% - 50%
  • 没有太多查询,平均负载的磁盘 I/O<1%

在Kubernetes集群这样的动态环境中,prometheus的数据平面一般看起来是这种样式

  • 垂直维度表示全部存储的序列
  • 水平维度表示样本传播的时间

如:

requests_total{path="/status", method="GET", instance="10.0.0.1:80"}
requests_total{path="/status", method="POST", instance="10.0.0.3:80"}
requests_total{path="/", method="GET", instance="10.0.0.2:80"}

Prometheus按期为全部系列收集新数据点,这意味着它必须在时间轴的右端执行垂直写入。可是,在查询时,咱们可能但愿访问平面上任意区域的矩形(各类label条件)

所以为了可以在大量数据中有效地查找查询序列,咱们须要一个索引。

在Prometheus 1.x存储层能够很好地处理垂直写入模式,可是随着规模增大,索引或出现一些问题,所以在2.0版本中从新设计了存储引擎和索引,主要改造是:

样本压缩

现有存储层的样本压缩功能在Prometheus的早期版本中发挥了重要做用。单个原始数据点占用16个字节的存储空间。但当普罗米修斯每秒收集数十万个数据点时,能够快速填满硬盘。

但,同一系列中的样本每每很是类似,咱们能够利用这一类样品(一样label)进行有效的压缩。批量压缩一系列的许多样本的块,在内存中,将每一个数据点压缩到平均1.37字节的存储。

这种压缩方案运行良好,也保留在新版本2存储层的设计中。具体压缩算法能够参考:Facebook的“Gorilla”论文中

时间分片

咱们将新的存储层划分为块(block),每一个块在一段时间内保存全部序列。每一个块充当独立数据库。

这样每次查询,仅检查所请求的时间范围内的块子集,查询执行时间天然会减小。

这种布局也使删除旧数据变得很是容易(这在1.x的存储设计中是一个很耗时的操做)。但在2.x中,一旦块的时间范围彻底落后于配置的保留边界,它就能够彻底丢弃。

索引

通常prometheus的查询是把metric+label作关键字的,并且是很宽泛,彻底用户自定义的字符,所以没办法使用常规的sql数据库,prometheus的存储层使用了全文检索中的倒排索引概念,将每一个时间序列视为一个小文档。而metric和label对应的是文档中的单词。

例如,requests_total{path="/status", method="GET", instance="10.0.0.1:80"}是包含如下单词的文档:

  • __name__="requests_total"
  • path="/status"
  • method="GET"
  • instance="10.0.0.1:80"

基准测试

cpu、内存、查询效率都比1.x版本获得了大幅度的提高

具体测试结果参考:https://dzone.com/articles/pr...

故障恢复

若是您怀疑数据库中的损坏引发的问题,则能够经过使用storage.local.dirtyflag配置,来启动服务器来强制执行崩溃恢复。

若是没有帮助,或者若是您只想删除现有的数据库,能够经过删除存储目录的内容轻松地启动:

  • 1.中止服务:stop prometheus.
  • 2.删除数据目录:rm -r <storage path>/*
  • 3.启动服务:start prometheus

远程存储

Prometheus默认是本身带有存储的,保存的时间为15天。但本地存储也意味着Prometheus没法持久化数据,没法存储大量历史数据,同时也没法灵活扩展。
为了保证Prometheus的简单性,Prometheus并无从自身集群的维度来解决这些问题,而是定义了两种接口,remote_write/remote_read,将数据抛出去,你本身处理。

Prometheus的remote_storage 实际上是一个adapter,至于在adapter的另外一端是什么类型的时序数据库它根本不关心,若是你愿意,你也能够编写本身的adpater。

如:存储的方式为:Prometheus —-发送数据—- > remote_storage_adapter —- 存储数据 —-> influxdb。

prometheus经过下面两种方式来实现与其余的远端存储系统对接:

  • Prometheus 按照标准的格式将metrics写到远端存储
  • Prometheus 按照标准格式从远端的url来读取metrics

远程读

在远程读的流程当中,当用户发起查询请求后,Promthues将向remote_read中配置的URL发起查询请求(matchers,ranges),Adaptor根据请求条件从第三方存储服务中获取响应的数据。同时将数据转换为Promthues的原始样本数据返回给Prometheus Server。

当获取到样本数据后,Promthues在本地使用PromQL对样本数据进行二次处理。

远程写

用户能够在Promtheus配置文件中指定Remote Write(远程写)的URL地址,一旦设置了该配置项,Prometheus将样本数据经过HTTP的形式发送给适配器(Adaptor)。而用户则能够在适配器中对接外部任意的服务。外部服务能够是真正的存储系统,公有云的存储服务,也能够是消息队列等任意形式。

配置

配置很是简单,只须要将对应的地址配置下就行

remote_write:
  - url: "http://localhost:9201/write"

remote_read:
  - url: "http://localhost:9201/read"

社区支持

如今社区已经实现了如下的远程存储方案

  • AppOptics: write
  • Chronix: write
  • Cortex: read and write
  • CrateDB: read and write
  • Elasticsearch: write
  • Gnocchi: write
  • Graphite: write
  • InfluxDB: read and write
  • OpenTSDB: write
  • PostgreSQL/TimescaleDB: read and write
  • SignalFx: write

可使用读写完整的InfluxDB,咱们使用了多prometheus server同时远程读+写,验证了速度仍是能够的。而且InfluxDB生态完整,自带了不少管理工具。

容量规划

在通常状况下,Prometheus中存储的每个样本大概占用1-2字节大小。若是须要对Prometheus Server的本地磁盘空间作容量规划时,能够经过如下公式计算:

磁盘大小 = 保留时间 * 每秒获取样本数 * 样本大小

保留时间(retention_time_seconds)和样本大小(bytes_per_sample)不变的状况下,若是想减小本地磁盘的容量需求,只能经过减小每秒获取样本数(ingested_samples_per_second)的方式。

所以有两种手段,一是减小时间序列的数量,二是增长采集样本的时间间隔。

考虑到Prometheus会对时间序列进行压缩,所以减小时间序列的数量效果更明显。

其余

远程读写解决了Promtheus的数据持久化问题。使其能够进行弹性扩展。另外还支持联邦集群模式,用于解决横向扩展、网络分区的问题(如地域A+B+C的监控数据,统一汇总到D),联邦集群的配置将在后面的Promthues高可用文章中详细说明。

附:kubecon2018上讲Prometheus 2.0的帅哥

还有一本专门讲Prometheus的书:Prometheus: Up & Running(600多页...)

国内没找到卖的,找到了一本英文pdf的,还在翻译理解中,有新的内容会继续同步在这个系列博客

。。。又找到一本:https://www.prometheusbook.com/

参考资料:

本文为容器监控实践系列文章,完整内容见:container-monitor-book

相关文章
相关标签/搜索