数据采集
最前面
数据采集(scrape)是prometheus的核心服务。它有两种采集方式:pull和push,这里着重分析pull模式,push模式请看文档。
简易流程
代码目录
- scrape
- testdata // 测试数据
- manager.go // scrape管理器,负责主要业务逻辑
- scrape.go // 数据采集,实现具体的采集方法
- target.go // 采集目标
关键概念
-
scrapePool
scrapePool是target集合的采集管理器,每一个scrapePool都负责管理一组targetsGroup。每一个scrapePool都会运行单独的数据采集服务
sp.Sync(groups)
。当Discovery模块有更新数据时,通过discoveryManagerScrape.SyncCh()
将数据传入scrapeManager,执行reload()
方法更新scrapePool。type scrapePool struct { appendable Appendable // 数据存储的上层抽象 logger log.Logger mtx sync.RWMutex config *config.ScrapeConfig client *http.Client // http客户端 activeTargets map[uint64]*Target // 活动中的target droppedTargets []*Target // 已废弃的target loops map[uint64]loop // 启动器,实现loop接口,负责启动当前pool进行数据采集 cancel context.CancelFunc // 上下文取消事件 // loops构造器 newLoop func(scrapeLoopOptions) loop }
-
scrapeLoop
scrapeLoop是建立在单个target基础上,为target提供具体的采集服务。每一个scrapeLoop都有独立的生命周期。scrapeLoop实现了loop接口。
s := &targetScraper{Target: t, client: sp.client, timeout: timeout} l := sp.newLoop(scrapeLoopOptions{ target: t, // target scraper: s, limit: limit, // sample limit honorLabels: honorLabels, honorTimestamps: honorTimestamps, mrc: mrc, // relabel config }) sp.activeTargets[hash] = t sp.loops[hash] = l // loops启动运行 go l.run(interval, timeout, nil)
-
reloader()
核心方法,在Run()
方法中启动,用于监听外部的更新数据,执行reload()
方法。文章最后会重点分析这个方法。
for {
select {
// 监听到关闭信号就结束循环
case <-m.graceShut:
return
// 每5秒开始监听重载事件,限制更新次数
case <-ticker.C:
select {
case <-m.triggerReload:
// 由m.triggerReload触发事件
m.reload()
case <-m.graceShut:
return
}
}
}
-
fanoutStorage
在创建scrapeManager时使用,fanoutStorage是对存储的抽象。在scrape数据之后,通过fanoutStorage写入存储中。具体实现逻辑在storage模块中。
-
loop
loop是一个单向的控制器,它负责启动和关闭一个服务。这个过程是单向的,已关闭的服务不能重新启动。它定义了一组接口方法。
// loop的接口定义 type loop interface { run(interval, timeout time.Duration, errc chan<- error) stop() }
-
timeLimitAppender
timeLimitAppender用于限制数据的时效性。在数据提交storage进行存储时,这条数据的生成时间已超过10分钟,那么prometheus就会抛错。目前默认值10分钟,无法通过配置文件修改。
const maxAheadTime = 10 * time.Minute
工作流程
-
创建管理器
// fanoutStorage:存储抽象,数据采集完成之后通过它写入到存储中。 scrape.NewManager(log.With(logger, "component", "scrape manager"), fanoutStorage)
-
应用配置
scrapeManager创建之后,通过调用
ApplyConfig(cfg *config.Config)
方法进行配置加载。若当前scrapeManager中有启动的scrapePool且不在当前配置中,执行
sp.stop()
方法,关闭scrapePool下所有的loop,同时删除这个scrapePool。若scrapePool的配置信息和传入的配置数据不相同,执行sp.reload(cfg)
方法重载配置(删除老的loop,启动新的loop)。 -
启动管理器
核心函数,用于启动scrapeManager。
Run()
方法主要运行了两个服务:func (m *Manager) Run(tsets <-chan map[string][]*targetgroup.Group) error { // 服务1:配置重载服务,监听m.triggerReload go m.reloader() // 服务2:监听外部传入的tsets,当发生变化时,通过m.triggerReload通知reloader for { select { // 监听channel,发生变化就执行reload函数 case ts := <-tsets: // 更新manager中的目标组 m.updateTsets(ts) select { // 触发reload事件 case m.triggerReload <- struct{}{}: default: } // 触发关闭事件 case <-m.graceShut: return nil } } }
核心方法
m.reloader()
是scrapeManager启动的核心方法。在这里做详细的说明,一层层的分析它的执行过程。
-
reload()
m.reload()
是reloader()
里调用的具体执行方法,由ticker
限制执行频率,默认5s。
m.reload()
为scrapeManager的每一个未创建scrapePool的targetGroup创建scrapePool,并启动协程运行同步服务sp.Sync(groups)
。// 创建新的ScrapePool newScrapePool(scrapeConfig, m.append, m.jitterSeed, log.With(m.logger, "scrape_pool", setName))
// 启动服务 go func(sp *scrapePool, groups []*targetgroup.Group) { // 更新sp中的targets sp.Sync(groups) wg.Done() }(m.scrapePools[setName], groups)
-
sp.Sync(groups)
func (sp *scrapePool) Sync(tgs []*targetgroup.Group)
方法首先筛选tgs
中的target,判断是否它是否是有效的taregt(t.Labels().Len() >
),将无效的target存入scrapeManager的droppedTargets中。筛选完成之后,执行下一个逻辑方法sp.sync(all)
。
// target筛选,将有效的target存储all中
for _, t := range targets {
if t.Labels().Len() > 0 {
all = append(all, t)
} else if t.DiscoveredLabels().Len() > 0 {
sp.droppedTargets = append(sp.droppedTargets, t)
}
}
// 对比现在和传入的targets,启动采集任务
sp.sync(all)
- sp.sync(all)
func (sp *scrapePool) sync(targets []*Target)
,根据target的hash值对传入参数targets和sp.activeTargets进行对比。
若targets中的元素若不存在于activeTargets中,则新建scrapeLoop,运行后台采集服务,若存在,则更新数据内容。
// target不在sp.activeTargets中,新建scrapeLoop
s := &targetScraper{Target: t, client: sp.client, timeout: timeout}
l := sp.newLoop(scrapeLoopOptions{
target: t,
scraper: s,
limit: limit,
honorLabels: honorLabels,
honorTimestamps: honorTimestamps,
mrc: mrc,
})
// 更新
sp.activeTargets[hash].SetDiscoveredLabels(t.DiscoveredLabels())
若activeTargets中有targets不存在的元素,则停止对应的loop,删除scrapePool和target。
- sl.run()
每一个scrapeLoop都会启动一个后台服务,对应代码go l.run(interval, timeout, nil)
。
run()
负责采集target的数据并且写入存储中。以下是关键步骤。
// 使用GET请求拿到exporter数据,将数据copy到缓存buf
contentType, scrapeErr := sl.scraper.scrape(scrapeCtx, buf)
// 将数据写入到存储中
// 存储时使用了scrapeCache,提高存储效率,具体逻辑在storage模块中实现!!!
total, added, seriesAdded, appErr := sl.append(b, contentType, start)
// 记录这次采集过程的元数据
sl.report(start, time.Since(start), total, added, seriesAdded, scrapeErr)
// run()的结束方法。循环结束之后执行。
sl.endOfRunStaleness(last, ticker, interval)