Prometheus监控平台
前言
最近工作中频繁的使用到了prometheus,所以也希望再深入了解一下这个系统。
系统架构
普罗米修斯系统的架构如图所示,其提供的功能大致上可以分为以下三类
- 数据采集
- 数据存储
- 数据查询
数据采集
从架构图的左侧可以看出,prometheus采用的是拉取的方式从目标服务拉取指标(时序数据),拉取目标的信息配置中在prometheus.yml文件中。系统会每隔一定时间(默认15s)去目标服务器拉取。
目标服务需要启动一个http服务,开放端口供prometheus拉取数据(prometheus提供了客户端api,不需要另外实现http服务器)。
关于为什么是pull而不是push的问题,prometheus官方给出了答复
使用拉取的方式有以下优势:
- 服务不需要知道监控平台的存在,只需要收集指标数据,可以根据需要增加实例
- 如果目标实例挂掉,监控平台可以很容易地知道(无法成功拉取)
- 你可以手动指定一个目标,并通过浏览器检查该目标实例的监控状况
当然,对于特殊情况只能使用push的场景,比如监控平台搭建在外网,服务在内网或者服务存活时间非常短暂。prometehus也提供了push网关。
目标主机可以上报数据到pushgateway,,然后prometheus server统一从pushgateway拉取数据
数据抓取基本逻辑:
数据抓取的实现主要在系统的scrape模块当中,由scrape_manager统一管理;
启动时将配置文件中需要监控的target列表传入,并生成target对象;
// Run receives and saves target set updates and triggers the scraping loops reloading.
// Reloading happens in the background so that it doesn't block receiving targets updates.
func (m *Manager) Run(tsets <-chan map[string][]*targetgroup.Group) error {
go m.reloader()
for {
select {
case ts := <-tsets:
m.updateTsets(ts)
select {
case m.triggerReload <- struct{}{}:
default:
}
case <-m.graceShut:
return nil
}
}
}
update函数会生成target对象,并会调用内部sync,通过开多个携程来定时调用scrapeAndReport函数
拉取目标数据
源码解析可以参考:ray1888.github.io/2019/10/06/…
数据存储
向Target拉取到数据之后就需要存储到数据库中,prometheus数据存储的是时序数据,大致的数据结构如下
type sample struct {
t int64
v float64
}
每个采样点sample由时间和值组成。
相同的label集合为一个series
,其和时间的关系可以总结为下面这张图
对数据的操作基本可以总结为———垂直写,水平读
读取一个时间范围内的数据,写入当前时刻不同series的数据
我们先放下具体的数据结构的实现,看看prometheus是的文件结构是什么样子的。
prometheus文件结构
Prometheus默认使用的是存储在本地磁盘的时间序列数据库,同时也支持与远程存储系统集成。
promethus的本地时序数据库的存储结构主要分为三个部分
block
:持久化到硬盘中的数据,2小时的数据为1个块,后期会被压缩的更久chunks_head
: 当前在写入的 block 对应的 chunks 文件,WAL
:预写式日志( WAL ),用于确保数据完整性
我们用docker部署prometheus后,可以看到目录下的文件结构如下
前面的每个文件夹是一个block,是持久化到内存中的时序数据。从时间上看后期大概被压缩到了大概是每18个小时一个block。但最近的block都是2小时一个block。
block
每个block中包含以下几个部分
chunk
:是一个子目录,包含了若干个从000001
开始编号的文件。文件中存储的就是在时间窗口[minTime,maxTime]以内的所有样本数据samples
,本质上就是对于内存中符合要求的memChunk
的持久化。index
: 存储了索引相关的内容meta数据
:包含了当前Block的元数据信息tombstones
: 用于存储对于时间序列的删除记录。如果删除了某个时间序列,Prometheus并不会立即对它进行清理,而是会在tombstones
做一次记录,等到下一次Block压缩合并的时候统一清理。
meta 元数据
meta数据结构如图: 其中
-
ulid
:用于识别这个Block的编号,它与Block的目录名一致 -
minTime
和maxTime
:表示这个Block存储的数据的时间窗口 -
stats
:表示这个Block包含的样本sample
, 时间序列series
以及chunks
数目 -
compaction
:这个Block的压缩信息,因为随着时间的流逝,多个Block也会压缩合并形成更大的Block。level
字段表示了压缩的次数,刚从内存持久化的Block的level
为1,每被联合压缩一次,子Block的level
就会在父Block的基础上加一,而sources
字段则包含了构成当前这个Block的所有祖先Block的ulid
。事实上,对于level >= 2
的Block,还会有一个parent
字段,包含了它的父Block的ulid
以及时间窗口信息。
Index -- 快速检索的关键
block的索引结构index大概如下
主要可以分为以下几个部分:
- TOC:TOC包含了整个
index
索引文件的全局信息,存储的内容是其余六部分的位置信息,即它们的起始位置在index
文件中的偏移量。 - Symbol Table:Symbol Table符号表存储的就是在[minTime, maxTime]范围内的时间序列的所有label的key和value集合(做了去重和排序),并且为每个symbol进行了编号。metric名字也是一种label(KEY是__name__)
- Series:存储的是时间序列
series
的相关信息,首先存储series
的各个label(是对应key和value在Symbol Table
中的编号)。紧接着存储series
相关的chunks
信息,包含每个chunk的时间窗口,以及该chunk在chunks
子目录下具体的位置信息。
- Label Index:存储了各个label的key和它所有可能的value的关联关系。例如,对于一个有着四个不同的value的key,它在这部分存储的条目如下所示:
Label Index Table:存储了所有label的key,以及它们在Label Index
中对应的位置信息。那么为什么要将这两部分的内容分开存储呢(Label Index部分没有key的信息)?
Prometheus在读取Block中的数据时会加载index
文件,但是只会首先加载Label Index Table
获取所有label的key,只有在需要对key相关的value进行匹配时,才会加载Label Index
相应的部分以及对应的Symbol。通过Label Index Table
和Label Index
的分离,使得我们能够只对必要数据进行加载,从而加快了index
文件的加载速度。
Postings: 这部分存储的是倒排索引的信息,每一个条目存储的都是包含某个label pair的所有series
的ID。但是与Label Index
相似,条目中并没有指定具体的key和value。
Postings Offset Table:类似Label Table,这部分直接对每个label的key和value以及相关索引在Postings
中的位置进行存储。
同样,它会首先被加载到内存中,如果需要知道包含某个label的所有series
,再通过相关索引的偏移位置从Postings
中依次获取。
chunk
chunk 文件夹下存储的具体的时序数据,具体的监控数据 每个文件的最大大小为 512MB。 每个chunk文件的结构如下 其中每条chunk就是一条数据
chunk_head & WAL
是当前写入的块,程序通过mmap进行写入,减少io次数,每隔一定时间,cpu会从内存中将数据刷入硬盘
wal和大多数的数据库一样,写入日志,防止意外崩溃丢失数据
整个存储流程
了解完了prometheus的整个存储的文件结构,我们再过一遍的他的存储流程
我们每次取抓取到一个时间序列(t,v),会将数据写入Chunk_Head块;为了防止内存数据丢失先做一次预写日志 (WAL) 。这个数据会在内存中停留一段时间,然后刷新到磁盘(M-map) 。当这些内存映射的块老化到某个时间点时(2个小时),会作为持久块Block存储到磁盘。接下来多个Block在它们变旧时被合并,并在超过保留期限后被清理。
默认情况下,每两个小时,会将内存中的chunk刷入硬盘进行保存
PS.mmap
我们传统的IO方式,底层实际上通过调用read()和write()来实现。通过read()把数据从硬盘读取到内核缓冲区,再复制到用户缓冲区;然后再通过write()写入到socket缓冲区,最后写入网卡设备。速度较慢
mmap实际上是将文件的地址映射到了进程中用户缓冲区,进程通过读写进程的用户缓存区,即可直接写入文件,不需要通过cpu将数据搬运到内核空间,再通过DMA写入文件。
存储的数据结构
程序运行的过程中,内存的时间序列的数据结构大致如下
type memSeries stuct {
......
ref uint64 // 其Seriesid
lst labels.Labels // 对应的标签集合
chunks []*memChunk // 数据集合
headChunk *memChunk // 正在被写入的chunk
......
}
type Label struct {
Name, Value string
}
这个series结构会保存序列的label集合,有这个序列数据的chunk列表以及正在被写入的chunk指针 每次获得到新的时序时序数据后,就会写入到headChunk中。
prometheus内存中维护了一个id到series的hash map,可以快速拿到对应的数据的memSeries
同时,通过对label作hash,也能快速得到memseries,因为也同样维护了一张map
type stripeSeries struct {
series [stripeSize]map[uint64]*memSeries // 记录refId到memSeries的映射
hashes [stripeSize]seriesHashmap // 记录hash值到memSeries,hash冲突采用拉链法
locks [stripeSize]stripeLock // 分段锁
}
type seriesHashmap map[uint64][]*memSeries
同时因为go的map不是线程安全的,通过加分段锁,即保证了线程安全,也防止锁的范围太大,影响性能。
数据检索
数据检索流程
数据存储到硬盘后,如何能够快速的寻址series,就需要靠前面讲到的Index结构了,
比如我们给出几条时间序列的查询条件
{__name__:http_requests}{group:canary}{instance:0}{job:api-server}
{__name__:http_requests}{group:canary}{instance:1}{job:api-server}
{__name__:http_requests}{group:production}{instance:1}{job,api-server}
{__name__:http_requests}{group:production}{instance:0}{job,api-server}
prometheus利用类似文档搜索的倒排索引,引入以Label为key的倒排索引(前面讲的index中的posting)
从posting table中查到这个label对于posting序号,然后获取到每一个label对应的seriesID列表
通过取交集的形式,快速获得满足要求的series序号
然后通过查找series表,快速定位到相关的chunk位置
Prometheus如何做到高可用?
在Prometheus官方的推荐方案中,对于高可用的处理是通过部署多套Prometheus,配置同样的目标实例来实现的。在这个方案里面,多套Prometheus会获取相同的监控指标,并且触发同样的告警规则,而对于警报的去重工作则由Alertmanager来负责。 但此方案也存在着明显缺点,比如当某个Prometheus出现故障或中断时,那么该节点将会出现数据丢失的情况,并与另一个节点存在数据差异。当在该节点上进行查询操作时,就会遇到这个问题。
对此,我们可以与远程存储方案结合起来,将Prometheus的读写放到远程存储端,通过高可用 远程存储的方式来解决上面的问题。
Prometheus也支持集群模式,在大规模的监控环境中,当单个Prometheus无法处理大量的监控采集任务时,我们可以基于联邦的模式将采集任务划分到不同的Prometheus实例中,再由顶层的Prometheus进行数据的统一管理。
在此方案中,工作节点的Prometheus根据拆分原则,负责指定目标的数据采集及规则告警工作,而主节点则通过/federate接口从工作节点获取数据指标,并写入到远程存储中,同时对接Grafana实现监控展示。
Promql
prometheus中查询数据使用的是promql。
这篇好文章是转载于:学新通技术网
- 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
- 本站站名: 学新通技术网
- 本文地址: /boutique/detail/tanfbaea
-
photoshop保存的图片太大微信发不了怎么办
PHP中文网 06-15 -
Android 11 保存文件到外部存储,并分享文件
Luke 10-12 -
《学习通》视频自动暂停处理方法
HelloWorld317 07-05 -
word里面弄一个表格后上面的标题会跑到下面怎么办
PHP中文网 06-20 -
photoshop扩展功能面板显示灰色怎么办
PHP中文网 06-14 -
微信公众号没有声音提示怎么办
PHP中文网 03-31 -
excel下划线不显示怎么办
PHP中文网 06-23 -
怎样阻止微信小程序自动打开
PHP中文网 06-13 -
excel打印预览压线压字怎么办
PHP中文网 06-22 -
TikTok加速器哪个好免费的TK加速器推荐
TK小达人 10-01