Prometheus源码学习(2) 服务发现

Prometheus 每个被控目标暴露一个 endpoint 供 server 抓取,要获知这些 endpoint 有多种方式,最简单的是在配置文件里静态配置,还有基于 k8s、consul、dns 等多种方式,基于文件的服务发现是比较灵活普遍的一种方式。当监控目标量比较大,变化的频率和量也比较大的时候,用 file SD 比较合适,我尝试过 consul,因为每次更新都要删除全量数据重新填充,所以不太适用这个场景。下文以 file SD 为例研究服务发现。

概要介绍(from README.md)

SD 的作用是提取所有信息提供给用户,用户可以用 relabeling 过滤出他们有用的。这些信息称即元数据。

每个 target 通过一组键值对暴露元数据。键有__meta前缀:__meta_<sdname>_<key>,还有一个 __address__ 标签包含 host:port。

SD 应该是泛用于一类服务发现的,不要硬编码任何定制化设置,而应该通过 relabel 实现过滤、转换或者业务逻辑。

如果一次 SD 处理发生异常导致失败,可能已经获取了部分结果,放弃这次处理而不要返回部分结果。继续使用旧的 target 数据好过部分或者错误的元数据。 这一点很有意义,我有两万多 targets,有时候写 SD 文件写错,如果 Prometheus 读取了一部分或者读出错就清空 target,后果就惨了。

Prometheus 不保证 SD 数据的安全。

写 SD 需要实现 Discoverer 接口(Run)方法,Prometheus 调用 Run() 初始化服务发现,发送所有 target group 到一个 channel 里,然后开始监视 SD 的变化,每次更新会发送全部或者只发送变化和新增的 target group 到 channel,这由 Manager 处理。

每个 target group 有一个全局唯一的 Source。例如一个文件名 file1,如果有两个 target group,某一次更新后其中一个发生了变化,那么就只把这个 target group 发送到 channel 中。如果某个 target group 中的 target 都不在了,就向 channel 发送一个空的 Targets

目录结构

.
├── README.md
├── config
├── manager.go
├── manager_test.go
├── targetgroup
└── discoverer

其中discoverer 是各种具体的服务发现机制的实现。

文件的开头声明了多个指标类型变量,在各个动作点进行相应的指标记录。

manager

manager 有一个构造函数和四个方法会被 main.go 调用

  • NewManager 构造函数,main.go 构造了两个 discovery.Manage 对象:discoveryManagerScrapediscoveryManagerNotify
  • Name 构造函数的参数
  • ApplyConfig 配置构造函数
  • Run 执行服务发现
  • SyncCh 获取 target group 的 channel

1. manager 结构体

manager.go 头部的导入显示它依赖各个具体的 Discoverer 实现。每个 discovery provider 有一个 channel,每次更新向其中发送 target group。

// Manager maintains a set of discovery providers and sends each update to a map channel.
// Targets are grouped by the target set name.
// Manager 维护一组服务发现的 provider, 将更新发送至一个 map channel。
type Manager struct {
    
    
	logger         log.Logger
	name           string
	mtx            sync.RWMutex
	ctx            context.Context
	discoverCancel []context.CancelFunc

	// Some Discoverers(eg. k8s) send only the updates for a given target group
	// so we use map[tg.Source]*targetgroup.Group to know which group to update.
	// 有些 Discoverers(例如 k8s)只发送给定的某个 taget group 的更新
	// 所以利用这个字典得知更新哪个 target group
	// targets 中保存着全部 target 数据
	targets map[poolKey]map[string]*targetgroup.Group
	// providers keeps track of SD providers.
	// 可以配置多个服务发现器
	providers []*provider
	// The sync channel sends the updates as a map where the key is the job value from the scrape config.
	// channel 元素是 map, key 是 prometheus 配置文件的 job_name,value 是其对应的 targetgroup。
	syncCh chan map[string][]*targetgroup.Group

	// How long to wait before sending updates to the channel. The variable
	// should only be modified in unit tests.
	updatert time.Duration

	// The triggerSend channel signals to the manager that new updates have been received from providers.
	// 这是一个用于通知 manager 有 provider 进行了更新的 channel
	triggerSend chan struct{
    
    }
}

比较重要的成员是 targets,它保存了全量的 target,poolKey 是一个结构体,由 job_name 和 provider_name 组成

type poolKey struct {
    
    
	setName  string
	provider string
}

通过 m.registerProviders 可以看到 setName 就是 “file”/“dns”/“consul”…,provider 是 provider 对象的 name 字段,是 “file”/“dns”/“consul”… 后面跟上这个 m.provider 有多少个发现文件,比如 file_SD_discovrer 配置了3个yml文件,poolKey 的 provider 字段就是 “file/3”

2. NewManager()

manager 构造函数,main.go 中调用它,初始化成员变量,updateert 默认5秒钟。可选配置的初始换使用了 functional optianls 模式值得学习

// NewManager is the Discovery Manager constructor.
func NewManager(ctx context.Context, logger log.Logger, options ...func(*Manager)) *Manager {
    
    
	if logger == nil {
    
    
		logger = log.NewNopLogger()
	}
	mgr := &Manager{
    
    
		logger:         logger,
		syncCh:         make(chan map[string][]*targetgroup.Group),
		targets:        make(map[poolKey]map[string]*targetgroup.Group),
		discoverCancel: []context.CancelFunc{
    
    },
		ctx:            ctx,
		updatert:       5 * time.Second,
		triggerSend:    make(chan struct{
    
    }, 1),
	}
	for _, option := range options {
    
    
		option(mgr)
	}
	return mgr
}

// Name sets the name of the manager.
func Name(n string) func(*Manager) {
    
    
	return func(m *Manager) {
    
    
		m.mtx.Lock()
		defer m.mtx.Unlock()
		m.name = n
	}
}

3. ApplyConfig()

  • 配置 provider
  • discoveredTargetsfailedConfigs 是 prometheus 自己的 metric。
  • registerProviders 方法向 Manager.providers 中追加各类 provider,每类可以有多个,如果已经有了这个 provider,就追加它的订阅者(subscribers)
// ApplyConfig removes all running discovery providers and starts new ones using the provided config.
// 停止所有正在运行的服务发现器,启动配置文件(prometheus.yml)设置的新发现器。cfg 的 key 是 job name。
func (m *Manager) ApplyConfig(cfg map[string]sd_config.ServiceDiscoveryConfig) error {
    
    
	m.mtx.Lock()
	defer m.mtx.Unlock()

	for pk := range m.targets {
    
    
		if _, ok := cfg[pk.setName]; !ok {
    
    
			discoveredTargets.DeleteLabelValues(m.name, pk.setName)
		}
	}
	// 停止正在运行的服务发现
	m.cancelDiscoverers()
	// 初始化 targets map
	m.targets = make(map[poolKey]map[string]*targetgroup.Group)
	// reset 各个字段
	m.providers = nil
	m.discoverCancel = nil

	failedCount := 0
	
	// name 是 job_name,scfg 是 SD_name
	for name, scfg := range cfg {
    
    
		// 注册服务发现器(provider),配置文件配了几个就注册几个
		failedCount += m.registerProviders(scfg, name)
		discoveredTargets.WithLabelValues(m.name, name).Set(0)
	}
	failedConfigs.WithLabelValues(m.name).Set(float64(failedCount))

	// 启动各个服务发现器
	for _, prov := range m.providers {
    
    
		m.startProvider(m.ctx, prov)
	}

	return nil
}

provider 是对 Discoverer 的一层包装,除了 Discoverer 以外还持有对 Discoverer 的订阅者。一个 SD 类型可以有多个 provider,一个 provider 如果有多个 job 配置了它,注意 provider 的 config 要相同,就追加它的订阅者(subs)。name 字段是 provider 类型名加当前 m
中包含的 provider 数量,其实就是这种类型 provider 的序号

// provider holds a Discoverer instance, its configuration and its subscribers.
type provider struct {
    
    
	name   string
	d      Discoverer
	subs   []string
	config interface{
    
    }
}

registerProviders 太长了,我就不列代码了,其中的 add 函数用法很值得学习。

startProvider

  • 初始化 updates channel 用于传递 target group
  • 启动 goroutine 开始运行服务发现,将更新的 target group 发送到 updates channel
  • 启动 goroutine,从 updates channel 接收数据,向 m.trggerSend 发送更新信号。
func (m *Manager) startProvider(ctx context.Context, p *provider) {
    
    
	level.Debug(m.logger).Log("msg", "Starting provider", "provider", p.name, "subs", fmt.Sprintf("%v", p.subs))
	ctx, cancel := context.WithCancel(ctx)
	updates := make(chan []*targetgroup.Group)

	m.discoverCancel = append(m.discoverCancel, cancel)

	go p.d.Run(ctx, updates)
	go m.updater(ctx, p, updates)
}

func (m *Manager) updater(ctx context.Context, p *provider, updates chan []*targetgroup.Group) {
    
    
	for {
    
    
		select {
    
    
		case <-ctx.Done():
			return
		case tgs, ok := <-updates:
			receivedUpdates.WithLabelValues(m.name).Inc()
			if !ok {
    
    
				level.Debug(m.logger).Log("msg", "Discoverer channel closed", "provider", p.name)
				return
			}

			for _, s := range p.subs {
    
    
				// 更新 Manager 持有的 target group
				m.updateGroup(poolKey{
    
    setName: s, provider: p.name}, tgs)
			}

			select {
    
    
			// 发送更新信号
			case m.triggerSend <- struct{
    
    }{
    
    }:
			default:
			}
		}
	}
}

updater 方法当 provider 更新从通道接受 target group 保存到 Manager 的 targets 字段中。发送信号的时候使用 select case 是因为 triggerSend 是一个无缓冲通道,要有人接收才能发送。Discoverer 的 Run 方法后面单独分析。

4. Run()

在新的 goroutine 运行 sender 方法

// Run starts the background processing
func (m *Manager) Run() error {
    
    
	go m.sender()
	for range m.ctx.Done() {
    
    
		m.cancelDiscoverers()
		return m.ctx.Err()
	}
	return nil
}

sender 通过一个计时器达到限制更新速率的目的,因为有些 discoverer 可能会过于频繁的更新 target。每次 Run() 都会根据 context 执行取消发现的操作。周期计时器用法值得学,注意创建以后要延迟关闭。

每5秒检查一次 m.triggerSend 中有没有更新的信号,如果有更新的信号,就组装 map[string][]*targetgroup.Group 发送到 m.SyncCh 中,由于 m.SyncCh 是无缓冲通道,如果没能接收的话,就等到下次检查到更新信号再重试发送,这里的嵌套 select case 非常值得学习

func (m *Manager) sender() {
    
    
	ticker := time.NewTicker(m.updatert)
	defer ticker.Stop()

	for {
    
    
		select {
    
    
		case <-m.ctx.Done():
			return
		case <-ticker.C: // Some discoverers send updates too often so we throttle these with the ticker.
			select {
    
    
			case <-m.triggerSend:
				sentUpdates.WithLabelValues(m.name).Inc()
				select {
    
    
				case m.syncCh <- m.allGroups():
				default:
					delayedUpdates.WithLabelValues(m.name).Inc()
					level.Debug(m.logger).Log("msg", "Discovery receiver's channel was full so will retry the next cycle")
					select {
    
    
					case m.triggerSend <- struct{
    
    }{
    
    }:
					default:
					}
				}
			default:
			}
		}
	}
}

m.allGroups 方法读取自身的 targets 成员变量中的值组装成 map 返回给调用者,用于向自身的 syncCh 发送这个 map,最终通知给 scraper。

当 SD 发现删除了某个 target group 时会发送一个空的 target group,此处对这个动作的意义做了说明,空的 target group 会通知 scraper 停止再抓取这些 target。

func (m *Manager) allGroups() map[string][]*targetgroup.Group {
    
    
	m.mtx.RLock()
	defer m.mtx.RUnlock()

	tSets := map[string][]*targetgroup.Group{
    
    }
	for pkey, tsets := range m.targets {
    
    
		var n int
		for _, tg := range tsets {
    
    
			// Even if the target group 'tg' is empty we still need to send it to the 'Scrape manager'
			// to signal that it needs to stop all scrape loops for this target set.
			tSets[pkey.setName] = append(tSets[pkey.setName], tg)
			n += len(tg.Targets)
		}
		discoveredTargets.WithLabelValues(m.name, pkey.setName).Set(float64(n))
	}
	return tSets
}

5. SyncCh()

返回一个只读的 channel,调用者通过这个 channel 接收更新的 target

// SyncCh returns a read only channel used by all the clients to receive target updates.
func (m *Manager) SyncCh() <-chan map[string][]*targetgroup.Group {
    
    
	return m.syncCh
}

6. 小结

在这里插入图片描述

至此,discover.Manager 的主要功能就捋出来了

  1. 主程序调用 NewManager() Manager 实例
  2. 主程序调用 m.ApplyConfig() 根据配置文件配置并启动 Manager 实例,Manager 实例包括一组 Provider,其持有具体的 Discoverer,Discoverer 在运行时定期刷新target group,通过 channel 发送给 Manager 将其保存在 m.targets 中,并向 m.triggerSend channel 发送通知信号
  3. m.Run() 按照 m.updatert 设定的时间间隔检查有没有更新的信号,有的话就把自己的 targets 字段中的 targets 发送给自身的 m.syncCh channel
  4. 主程序调用 m.SyncCh() 方法获取 channel 并从中取得 target

我配置了 file SD

scrape_configs:
  - job_name: node1
    file_sd_configs:
    - files:
      - 'node_targets1.yml'
      refresh_interval: 10s
    - files:
      - 'node_targets2.yml'
      refresh_interval: 20s
  - job_name: node2
    file_sd_configs:
    - files:
      - 'node_targets1.yml'
      refresh_interval: 10s
    - files:
      - 'node_targets3.yml'
      refresh_interval: 40s

启动 Prometheus 后 debug 到如下日志

level=debug ts=2021-01-19T06:28:25.951Z caller=manager.go:224 component="discovery manager scrape" msg="Starting provider" provider=*file.SDConfig/1 subs="[node1 node2]"
level=debug ts=2021-01-19T06:28:25.951Z caller=manager.go:224 component="discovery manager scrape" msg="Starting provider" provider=*file.SDConfig/2 subs=[node1]
level=debug ts=2021-01-19T06:28:25.951Z caller=manager.go:224 component="discovery manager scrape" msg="Starting provider" provider=*file.SDConfig/3 subs=[node2]

可以有多个不同类型的 provider,每种类型又可以有多个 provider。一共启动了三个 scrape manager 一个 notify manager,channel 在启动后就关闭了,这个后面研究一下。

Discoverer

discoverer 是个接口,只有一个 Run() 方法,m.startProvider() 会执行这个方法,其参数有一个传递更新的 targetgroup 的通道。

// Discoverer provides information about target groups. It maintains a set
// of sources from which TargetGroups can originate. Whenever a discovery provider
// detects a potential change, it sends the TargetGroup through its channel.
//
// Discoverer does not know if an actual change happened.
// It does guarantee that it sends the new TargetGroup whenever a change happens.
//
// Discoverers should initially send a full set of all discoverable TargetGroups.
// Discoverer 提供监控目标的信息。当服务发现的提供者(具体的file/consul等discovery)发现监控目标变化的时候
// 会通过通道发送监控目标。
// Discoverer 不知道具体发生了哪些变化,它只保证当变化发生后发送当前全量的监控目标。
// Discoverer 初始发送可发现的全量监控目标。
type Discoverer interface {
    
    
	// Run hands a channel to the discovery provider (Consul, DNS etc) through which it can send
	// updated target groups.
	// Must returns if the context gets canceled. It should not close the update
	// channel on returning.
	// Run 交给 discovey 提供者(Consul、DNS...)一个 channel,通过这个 channel 传递更新的监控目标。
	// context 取消时必须马上返回。返回时不应该关闭 channel
	Run(ctx context.Context, up chan<- []*targetgroup.Group)
}

file Discovery

discovery/file.go 中是 file Discovery 的具体实现

1. Discovery

  • 内部的同步保护锁是值类型,所以方法的接收者要是指针类型
// Discovery provides service discovery functionality based
// on files that contain target groups in JSON or YAML format. Refreshing
// happens using file watches and periodic refreshes.
// Discovery 提供基于文件的服务发现,文件格式可以是 JSON 或 YAML。
// 通过监视文件变化和定期执行来刷新监控目标
type Discovery struct {
    
    
	// 文件路径列表
	paths []string
	// 监视器
	watcher *fsnotify.Watcher
	// 更新间隔
	interval time.Duration
	// 每个文件的最后一次更新时间
	timestamps map[string]float64
	// 内部保护同步的锁,注意不是 *sync.RWMutex,
	// 所以 Discovery 结构体的方法定义的 receiver 必须是 *Discovery。
	lock sync.RWMutex

	// lastRefresh stores which files were found during the last refresh
	// and how many target groups they contained.
	// This is used to detect deleted target groups.
	// 最后一次刷新发现的文件以及这个文件包含的监控目标数量。
	// 用于发现删除的监控目标
	lastRefresh map[string]int
	logger      log.Logger
}

2. NewDiscovery()

m.registerProviders 调用这个构造函数,

// NewDiscovery returns a new file discovery for the given paths.
func NewDiscovery(conf *SDConfig, logger log.Logger) *Discovery {
    
    
	if logger == nil {
    
    
		logger = log.NewNopLogger()
	}

	disc := &Discovery{
    
    
		// 服务发现文件名
		paths:      conf.Files,
		// 服务发现间隔
		interval:   time.Duration(conf.RefreshInterval),
		// 每个文件的最后一次更新时间
		timestamps: make(map[string]float64),
		logger:     logger,
	}
	fileSDTimeStamp.addDiscoverer(disc)
	return disc
}

conf 是 *SDConfig 类型,文件名和更新间隔定义了一个 SDConfig 实例

// SDConfig is the configuration for file based discovery.
type SDConfig struct {
    
    
	Files           []string       `yaml:"files"`
	RefreshInterval model.Duration `yaml:"refresh_interval,omitempty"`
}

3. Run()

  • 其中 refresh() 是刷新 target group 的方法,Run() 是一个后台常驻的 goroutine,第一次被调用的时候初始填充一次监控对象,然后定期更新。
  • 如果监控到文件权限变更就忽略,我第一次读到这里认为更名操作也应该被忽略,后来明白更名操作要更新文件名对应的监控目标,所以一旦更名就要刷新一次。
// Run implements the Discoverer interface.
// Run 实现 Discoverer 接口
func (d *Discovery) Run(ctx context.Context, ch chan<- []*targetgroup.Group) {
    
    
	// fsnotify 监控视文件(夹)的变化,发生变化的时候向通道发送通知事件
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
    
    
		level.Error(d.logger).Log("msg", "Error adding file watcher", "err", err)
		return
	}
	d.watcher = watcher
	defer d.stop()

	// 初始填充一次监控目标
	d.refresh(ctx, ch)

	// 无论是否监控到文件(夹)发生变化,都在一段时间间隔后执行一次服务发现。
	ticker := time.NewTicker(d.interval)
	defer ticker.Stop()

	for {
    
    
		select {
    
    
		// context 取消时立即返回
		case <-ctx.Done():
			return

		// 监控到文件(夹)变化
		case event := <-d.watcher.Events:
			// fsnotify sometimes sends a bunch of events without name or operation.
			// It's unclear what they are and why they are sent - filter them out.
			// fsnotify 有时会发送一批没有名称或者操作类型的事件,忽略它们。
			if len(event.Name) == 0 {
    
    
				break
			}
			// Everything but a chmod requires rereading.
			// 如果操作类型只是一个权限变更,忽略它。
			if event.Op^fsnotify.Chmod == 0 {
    
    
				break
			}
			// Changes to a file can spawn various sequences of events with
			// different combinations of operations. For all practical purposes
			// this is inaccurate.
			// The most reliable solution is to reload everything if anything happens.
			// 对一个文件的变化会触发一系列不同操作类型组合的事件,不管发生什么变化都重新加载
			// 监控目标
			d.refresh(ctx, ch)

		case <-ticker.C:
			// Setting a new watch after an update might fail. Make sure we don't lose
			// those files forever.
			// 在一次更新之后设置新的文件监视器可能会失败。设置定期更新保证不会永远错失更新。
			d.refresh(ctx, ch)

		case err := <-d.watcher.Errors:
			if err != nil {
    
    
				level.Error(d.logger).Log("msg", "Error watching file", "err", err)
			}
		}
	}
}

3.1 d.refresh()

  • d.listFiles() 提取配置文件中配置的正则表达式匹配的文件列表
  • d.readFile() 逐个读取每个文件,解析出 target group,更新这个文件的最后一次刷新时间
  • 把 target group 发送给传递的通道
  • 处理比上次刷新缺少的文件和 target group
  • 设置文件监视器
// refresh reads all files matching the discovery's patterns and sends the respective
// updated target groups through the channel.
// 读取配置文件里的匹配服务发现模板的全部文件,通过通道发送变更后的监控目标。
func (d *Discovery) refresh(ctx context.Context, ch chan<- []*targetgroup.Group) {
    
    
	// 这里会计算和更新 Prometheus 自身的监控指标,prometheus_sd_file_scan_duration_seconds_count,
	// 记录刷新监控目标花费的总耗时和每次刷新的耗时分布。
	t0 := time.Now()
	defer func() {
    
    
		fileSDScanDuration.Observe(time.Since(t0).Seconds())
	}()
	// ref 是临时的,最终赋值给 Discovery 结构体的 lastRefresh 变量。
	ref := map[string]int{
    
    }
	// 列举监控目标文件,匹配文件名的正则表达式,找出全部的监控目标文件,
	// p 是具体的监控目标文件的文件名。
	for _, p := range d.listFiles() {
    
    
		// 读取这个文件的全量 target
		tgroups, err := d.readFile(p)
		if err != nil {
    
    
			// 增加 prometheus_sd_file_read_errors_total 指标计数。
			fileSDReadErrorsCount.Inc()

			level.Error(d.logger).Log("msg", "Error reading file", "path", p, "err", err)
			// Prevent deletion down below.
			// 如果读取文件发生错误,保留上次刷新该文件的结果并
			// 跳过本次对这个文件的后续处理。
			ref[p] = d.lastRefresh[p]
			continue
		}
		select {
    
    
		// 将 target 发送给 channel
		case ch <- tgroups:
		// context 取消时立即返回
		case <-ctx.Done():
			return
		}
		// 读取完一个文件后将记录这个文件包含的 target 数量
		ref[p] = len(tgroups)
	}
	// Send empty updates for sources that disappeared.
	// 对于删除的监控目标文件发送空的更新。
	// f 和 n 是本次刷新前的监控目标文件和其中的 target 数量。
	for f, n := range d.lastRefresh {
    
    
		// 如果本次刷新后还有这个文件, ok 就是 true,m 是本次刷新后的 target 数量。
		m, ok := ref[f]
		// 如果删除了这个文件,或者这个文件当前的 target 数量较刷新前减少了,即删除了一些 target。
		if !ok || n > m {
    
    
			level.Debug(d.logger).Log("msg", "file_sd refresh found file that should be removed", "file", f)
			// 删除最后刷新时间字典中的该文件名为键的字典项。
			d.deleteTimestamp(f)
			// 每减少一个 target group 就像 channel 发送一个没有 Targets 和 Labels,
			// 仅有 Source 的 targetgroup
			for i := m; i < n; i++ {
    
    
				select {
    
    
				case ch <- []*targetgroup.Group{
    
    {
    
    Source: fileSource(f, i)}}:
				case <-ctx.Done():
					return
				}
			}
		}
	}
	// 更新 d.lastRefresh 为 ref
	d.lastRefresh = ref
	// 设置文件监视器
	d.watchFiles()
}

d.readFile()

// readFile reads a JSON or YAML list of targets groups from the file, depending on its
// file extension. It returns full configuration target groups.
// 读取单个监控目标文件,根据扩展名进行 JSON 或者 YAML 的相应解析,返回全部的监控目标。
func (d *Discovery) readFile(filename string) ([]*targetgroup.Group, error) {
    
    
	fd, err := os.Open(filename)
	if err != nil {
    
    
		return nil, err
	}
	defer fd.Close()

	content, err := ioutil.ReadAll(fd)
	if err != nil {
    
    
		return nil, err
	}

	info, err := fd.Stat()
	if err != nil {
    
    
		return nil, err
	}

	var targetGroups []*targetgroup.Group

	// 根据扩展名进行相应解析
	switch ext := filepath.Ext(filename); strings.ToLower(ext) {
    
    
	case ".json":
		if err := json.Unmarshal(content, &targetGroups); err != nil {
    
    
			return nil, err
		}
	case ".yml", ".yaml":
		if err := yaml.UnmarshalStrict(content, &targetGroups); err != nil {
    
    
			return nil, err
		}
	default:
		panic(errors.Errorf("discovery.File.readFile: unhandled file extension %q", ext))
	}

	for i, tg := range targetGroups {
    
    
		if tg == nil {
    
    
			err = errors.New("nil target group item found")
			return nil, err
		}
		// 每一组 target 的 Source
		tg.Source = fileSource(filename, i)
		if tg.Labels == nil {
    
    
			tg.Labels = model.LabelSet{
    
    }
		}
		// LabelSet 的 fileSDFilepathLabel 字段设为文件名
		tg.Labels[fileSDFilepathLabel] = model.LabelValue(filename)
	}
	// 每读取一个文件就更新这个文件的最后一次刷新时间
	d.writeTimestamp(filename, float64(info.ModTime().Unix()))

	return targetGroups, nil
}

d.watchFiles()

// watchFiles sets watches on all full paths or directories that were configured for
// this file discovery.
// 为配置的路径设置监视器
func (d *Discovery) watchFiles() {
    
    
	if d.watcher == nil {
    
    
		panic("no watcher configured")
	}
	for _, p := range d.paths {
    
    
		if idx := strings.LastIndex(p, "/"); idx > -1 {
    
    
			// 如果是目录就去掉最后的斜线,应该是 fsnotify.Watcher 的要求
			p = p[:idx]
		} else {
    
    
			// 当前目录
			p = "./"
		}
		if err := d.watcher.Add(p); err != nil {
    
    
			level.Error(d.logger).Log("msg", "Error adding file watch", "path", p, "err", err)
		}
	}
}

在这里插入图片描述

小结


  1. 整个代码模块全部使用 unbuffered channel,
  2. Functional options
  3. manager 依赖 discoverer

猜你喜欢

转载自blog.csdn.net/qq_35753140/article/details/112999768