监控的未来
在上家公司的时候调研过监控,最开始后的时候用的cacti,后来用的zabbix再后来公司想着自研一套,于是我就去调研了,当时看到了falcon,说实话看完文档和使用方式,只能说很没有工程美感,简单看了下就跳过了,于是就自己写了一个采集的agent,在调研到存储和告警的时候,豁然发现了prometheus(2017.10),因为当时也在想监控系统应该是做成什么样子,当时想来想去都觉得,一定不是zabbix那样的,一个监控系统的采集、告警和存储应该解耦,并且通过适当的分层,最终到达一种db的模式,就是你想接入监控?ok那对我来说,你就是个简单的key、value其实,我想要加告警策略那么就类似写一条sql就行了,而prometheus就是这样的!真是工程美的典范,至于当前这家公司,17年入职以后用的是falcon,而且还各种自己魔改,把本来就不怎么样的代码魔改的像一坨emmmm,不吐槽了,浪费时间。关于监控的一部分思考可以看下这个链接.
监控的未来,一定是prometheus这样,接入方的数据就是简单的key-value,介入后配置规则进行告警
从接口开始
在我看来prometheus的核心一方面是他的数据存储设计,但是更核心的应该是他的PromQL这两个都很难,后者甚至语法解析词法解析这些东西(还觉得编译原理没用吗?),所以这里我还是从数据入口来进行学习,就是prom和agent的scrape接口。
接口数据的格式不是用的json,这里我怀疑是因为json的可读性不是太好,所以默认是用的自有格式,不过现在官方也提供了prom2json这样的工具,下载node_exporter启动之后访问目标机器的9100端口/metrics就可以拉取到监控数据了,如下
1 | # HELP go_gc_duration_seconds A summary of the GC invocation durations. |
#之后的算是监控项的定义和备注,HELP是对监控项的备注,TYPE是对指标的定义,之后下面的数据就是具体的监控指标后面中括号当中的是给监控项细分的label(这也是prometheus设计的非常好的一点,拓展性极强,数据格式非常清晰,后面讲到规则配置的时候还会继续细说),最后就是当前的数值。所以总结一下,数据行其实就是两部分,第一部分是监控指标也就是metric+labels(可选)第二部分是具体的数据。使用官方工具转换成json就是如下格式了,说实话感觉json可读性并不是很好
1 | [ |
之后来到prometheus/scrape里面主要包含三个主要文件manager.go/scrape.go/target.go从名字可以大致看到他们的主要功能:
manager主要是拉取目标的一些管理上的功能模块
主要包含一个Manager的结构体定义如下
1 | type Manager struct { |
target主要包含对于目标机器相关的操作
1 | type Target struct { |
scrape应该是最细粒度具体干活的逻辑代码主要包含一些一些结构
1 | //类似一个拉取池 |
他们的调用关系简单说一下就是manager创建scrapePool拉取池创建之后会创建一个scrapeLoop来循环拉取目标,如下
1 | func (m *Manager) reload() { |
在scrape.go里面可以看到targetScraper的scrape方法,这里就是对配置当中每个目标拉取数据的逻辑(相当于一个HTTP GET请求)
1 | func (s *targetScraper) scrape(ctx context.Context, w io.Writer) (string, error) { |
scrapeLoop它run起来之后就开始进入一个loop,定时从target当中拉取数据并进行解析
1 | func (sl *scrapeLoop) run(interval, timeout time.Duration, errc chan<- error) { |
这里我们可以看到在函数开始声明并创建了一个app、和p,这两个是这个部分主要干活的两个对象,appender我们可以看到是scrapeLoop创建时赋值给appender的一个函数方法,函数定义是func()storage.Appender,返回一个Appender对象,向上查找可以找到在manager当中创建拉取池的时候赋值指定的
1 | newScrapePool(cfg *config.ScrapeConfig, app storage.Appendable, jitterSeed uint64, logger log.Logger) (*scrapePool, error) |
在对返回内容进行解析的时候,会调用textparse.New生成一个如下的promlexer
说句题外话,看到代码里面有队openmetrics进行的支持和适配,看起来后续是打算支持新的格式,可以看下官方文档,项目官网,github,prometheus rouadMap,简介
1 | type promlexer struct { |
1 | func (sl *scrapeLoop) append(b []byte, contentType string, ts time.Time) (total, added, seriesAdded int, err error) { |
距离上次看已经过蛮久了,代码里面甚至都已经把scrapeLoop的report函数更改为了scrapeAndReport,由于从接口拿到的数据格式在解析过程中使用了编译原理当中的词法分析,这块先跳过,先看服务的整体流程,后面回过头再重新查看。
今天看到一篇介绍谷歌Monarch的文章,很硬核,看起来不错的,推荐,看样子是解决了分布式的问题,值得注意的是还使用了push的方式
存储
从上一段scrape代码里面可以看到在append里面,是存在将数据add到存储里面的这个步骤,因此从这里入手,可以看到当数据流入服务的存储核心代码的时候都经过了哪些处理,从sl.cache.get我们可以看出到,根据从服务cache中是否获取到,分别走了两个流程,命中之后的逻辑比较简单,未命中的逻辑稍微复杂一些。
可以看到AddFast的调用,通过向上一路追查
1 | func (f *fanoutAppender) AddFast(ref uint64, t int64, v float64) error { |
1 | func (f *fanout) Appender(ctx context.Context) Appender { |
向上一直找到fanout的定义和创建方式
1 | type fanout struct { |
以上代码来自storage/fanout.go,以下对于NewFanout的调用来自main.go,prometheus是支持写入本地的同时将数据写入远程存储的,比如influxdb,所以可以看到这里创建了两个实例,分别对应了本地和远程
1 | var ( |
以下内容来自tsdb/db.go,这里需要说一下tsdb的早期设计和实现是有fabxc实现的,可以看一下他的blog,原理出自该论文,
1 | func Open(dir string, l log.Logger, r prometheus.Registerer, opts *Options) (db *DB, err error) { |
在tsdb/head.go中我们可以看到AddFast的定义
1 | func (a *headAppender) AddFast(ref uint64, t int64, v float64) error { |
转载请注明来源链接 http://just4fun.im/2020/06/27/prometheus-interface/ 尊重知识,谢谢:)