Prometheus TSDB (Part 3): Memory Mapping of Head Chunks from Disk

数据采集

最前面

​ 数据采集(scrape)是prometheus的核心服务。它有两种采集方式:pull和push,这里着重分析pull模式,push模式请看文档

简易流程

代码目录
- scrape
	- testdata	// 测试数据
	- manager.go	// scrape管理器,负责主要业务逻辑
	- scrape.go	// 数据采集,实现具体的采集方法
	- target.go	// 采集目标
关键概念
  1. 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
    }
    
  2. 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)
    
  3. 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
			}
		}
	}
  1. fanoutStorage

    ​ 在创建scrapeManager时使用,fanoutStorage是对存储的抽象。在scrape数据之后,通过fanoutStorage写入存储中。具体实现逻辑在storage模块中。

  2. loop

    ​ loop是一个单向的控制器,它负责启动和关闭一个服务。这个过程是单向的,已关闭的服务不能重新启动。它定义了一组接口方法。

    // loop的接口定义
    type loop interface {
    	run(interval, timeout time.Duration, errc chan<- error)
    	stop()
    }
    
  3. timeLimitAppender

    ​ timeLimitAppender用于限制数据的时效性。在数据提交storage进行存储时,这条数据的生成时间已超过10分钟,那么prometheus就会抛错。目前默认值10分钟,无法通过配置文件修改。

    const maxAheadTime = 10 * time.Minute
    
工作流程
  1. 创建管理器

    // fanoutStorage:存储抽象,数据采集完成之后通过它写入到存储中。
    scrape.NewManager(log.With(logger, "component", "scrape manager"), fanoutStorage)
    
  2. 应用配置

    ​ scrapeManager创建之后,通过调用ApplyConfig(cfg *config.Config)方法进行配置加载。

    若当前scrapeManager中有启动的scrapePool且不在当前配置中,执行sp.stop()方法,关闭scrapePool下所有的loop,同时删除这个scrapePool。若scrapePool的配置信息和传入的配置数据不相同,执行sp.reload(cfg)方法重载配置(删除老的loop,启动新的loop)。

  3. 启动管理器

    ​ 核心函数,用于启动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启动的核心方法。在这里做详细的说明,一层层的分析它的执行过程。

  1. 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)
    
  2. 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)
  1. 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。

  1. 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)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值