从配置文件入手寻找代码逻辑

告警通知逻辑解析

解析起因:告警在触发后,每隔一定时间,就会重新发送一次,在 rule 配置文件的 interval 字段是配置的其中一个,但是通过抓包分析,应该还有另一个参数的时间,与之相加,才是总体的告警重发送间隔

首先找到 interval 字段的配置,由于该字段的默认值是 PrometheusServer 配置文件中的 .global.evaluation_interval 字段的值,所以从此处开始找起。

配置文件及其关联的结构体

在 ./config/config.go 文件中,找到了与该配置关联的结构体

// ./config/config.go
type GlobalConfig struct {
	// 其他结构体中的属性省略
	// How frequently to evaluate rules by default.
	EvaluationInterval model.Duration `yaml:"evaluation_interval,omitempty"`
}

通过查找引用,找到引用该属性的位置

为什么我就确定是这里呢?因为 main.go 文件中的另俩个引用是这样的:

// ./cmdg/prometheus/main.go
noStepSubqueryInterval.Set(config.DefaultGlobalConfig.EvaluationInterval)
// ./cmd/prometheus/main.go
noStepSuqueryInterval.Set(conf.GlobalConfig.EvaluationInterval)

而该属性在 config.go 文件中的调用是用来确定该字段的值的。比如,如果配置文件没有该字段,则默认 如何如何

所以,EvaluationInterval 真正被使用的地方,应该就是 Updata() 方法中的调用

找到使用该配置的函数

接下来跳转过来,这是一个作用在 ruleManager 上的 Update() 方法,第一个参数就是评估间隔

				// ./cmd/prometheus/main.go
				return ruleManager.Update(
					time.Duration(cfg.GlobalConfig.EvaluationInterval), // 评估间隔
					files,
					cfg.GlobalConfig.ExternalLabels,
				)

接下来查看 Update() 的逻辑,重点是 LoadGroups() 方法,使用到了 评估间隔 ,就是该方法的第一个参数 interval。

// ./rules/manager.go
func (m *Manager) Update(interval time.Duration, files []string, externalLabels labels.Labels) error {
	groups, errs := m.LoadGroups(interval, externalLabels, files...)
}

接下来查看 LoadGroups() 的逻辑

// ./rules/manager.go
func (m *Manager) LoadGroups(
interval time.Duration, externalLabels labels.Labels, filenames ...string,
) (map[string]*Group, []error) {
		for _, rg := range rgs.Groups {
			itv := interval // 将评估间隔的值,赋给 itv
			if rg.Interval != 0 { // 如果评估间隔的值不为0,那么评估间隔的值,就是 rule 配置文件中 interval 字段的值。
				itv = time.Duration(rg.Interval) // 这个其实也是评估间隔,rule 配置文件的值可以覆盖 PrometheusServer 配置文件中的值
			}

			groups[groupKey(fn, rg.Name)] = NewGroup(GroupOptions{
				Interval:      itv, // itv 的值,作为 NewGroup() 函数的返回值,用于实例化 Group 结构体
			})
		}
		return groups, nil
}

正向查找

从上面的代码可以看到,评估间隔 被用作 Group 这个结构体的属性了。并使用了 NewGroup() 实例化了该结构体,所以配置文件中评估间隔的值,也就被带到了 groups 这个变量中。所以,我们想继续追踪评估间隔,就应该开始追踪 groups 这个变量了,而 groups 变量就是实例化的 Group 结构体,也就是要从 Group 结构体入手。之后,groups 作为返回值,返回给了调用 LoadGroups() 的代码。所以,我们后面还需要返回调用了 LoadGroups() 的代码中,也就是 Update() 中继续寻找。

咱们先看一下 Group 结构体的属性,详见本文参考部分

Update() 更新规则

此时我们回到 Update() 方法中,可以看到调用了 LoadGroups() 并返回了 Group 结构体的实例,并赋值给 groups 变量,所以 groups 作为 Group 结构体的实例,就带有 评估间隔 的值。咱来看看 groups 都干啥了

首先看一下 Update 的注释:Update() 用于配置所需的规则管理器的状态。如果加载新规则失败,则将还原旧规则集。说白了,就是每次更新 rule 配置文件时,都是通过这个方法来执行具体的行为。

// ./rules/manager.go
func (m *Manager) Update(interval time.Duration, files []string, externalLabels labels.Labels) error {
	groups, errs := m.LoadGroups(interval, externalLabels, files...)
	// 配置文件中的多个组,通过循环,来逐一处理,每个组都是一些规则的集合。
	for _, newg := range groups {
		// 并发调用 run(),这里面也包含了 评估间隔
		go func(newg *Group) {
			newg.run(m.opts.Context)
		}(newg)
	}
}

去掉很多无用代码后,发现 Update 主要是并发调用 run() 方法,run() 是作用在 Group 这个结构体上的方法,所以这个方法里包含了 评估间隔。下面咱就来看看 run() 中又是如何执行,以及如何使用 评估间隔的。

run() 运行评估

实际上,在分析完成后再回来看,就知道,run() 就是 PrometheusServer 中执行规则管理的主要代码逻辑。其中就包括根据配置文件设置的时间间隔,来定期运行评估规则的代码,这个评估规则的代码就是 Eval() 方法。

接下来就是重点了,直接在 run() 方法中,搜索 g.interval 属性,找找有没有调用的地方,果不其然,找到了!!

首先,tick := time.NewTicker(g.interval) 这里使用 评估间隔 来生成了一个计时器,然后在一个 for{} 循环中,case <-tick.C 这个部分,持续使用计时器来运行 iter() 函数,然后可以看到,iter() 是一个无头函数,其中包含了一个名为 g.Eval() 的方法。

// ./rules/manager.go 代码中省略了无关内容
// run 方法就是 Prometheus Server 进行规则管理的主要逻辑。主要的逻辑就是运行 Eval() 来评估规则。
func (g *Group) run(ctx context.Context) {
	// 通过 g.interval 这个评估间隔时间,来生成一个时间戳
	// 等待初始量以保持一致的时段间隔。也就是用来设置一个时间戳,所有的等待时间都以该时间戳作为起点开始计算。
	evalTimestamp := g.evalTimestamp().Add(g.interval)
	ctx = promql.NewOriginContext(ctx, map[string]interface{}{
		"ruleGroup": map[string]string{
			"file": g.File(),
			"name": g.Name(),
		},
	})
	// Eval() 方法在此被调用,接下来,就要看看 iter() 被谁调用
	iter := func() {
		g.Eval(ctx, evalTimestamp)
	}

	// 使用配置文件中 评估间隔 的值,设置一个 Ticker。这个 tick 变量就是后面死循环会用到的真正的 评估间隔。
	tick := time.NewTicker(g.interval)
	defer tick.Stop()
	// 程序运行后,即进行一次评估
	iter()
	// 这里是一个死循环,也调用了评估行为,明显就是我们要找的按照特定时间周期运行的规则评估行为。
	for {
		select {
		case <-g.done:
			return
		default:
			select {
			case <-g.done:
				return
			// 调用 tick.C 每等待一个 评估间隔 的时间,都会执行一次规则评估的行为
			case <-tick.C:
				evalTimestamp = evalTimestamp.Add((missed + 1) * g.interval)
				iter()
			}
		}
	}
}

已找到评估间隔的作用位置

至此,我们已经看到了配置文件中,评估间隔 这个配置具体的应用方式,就是在 run() 方法中,被计时器调用,用来在固定的时间间隔,重复执行 Eval() 方法。

但是,文章开头的问题依然没有解决,在告警产生后并持续发送的时间间隔,除了评估间隔,还有另一个时间会被计算进来,那么我们还需要继续深入挖掘。除非,另一个时间是在评估间隔发生之前就存在的,不过,这就另说了,如果这条路走不通,再返回去找。

Eval() 评估规则

既然 run() 中主要运行的就是 Eval(),那我们来看看这个方法。追踪过去发现,该方法有一段注释:**Eval 用于运行一个单一的评估周期,在这个周期中,所有的规则都会被依次评估。**这看来就是评估规则的具体逻辑。在 Eval() 中,我们可以看到,其中有一个名为 sendAlerts() 方法,调用了 g.interval 的值。sendAlerts 故名思意,就是发送告警的意思。

看来我们没有找错,发送告警的行为是在评估规则的行为之后,那么这个发送间隔肯定也是在这个方法里。

// ./rules/manager.go
// Eval 是规则评估行为的主要逻辑。
func (g *Group) Eval(ctx context.Context, ts time.Time) {
	for i, rule := range g.rules {
		select {
		case <-g.done:
			return
		default:
		}

		func(i int, rule Rule) {
            ......
            // Eval() 是 Rule 接口中的方法。用来评估规则表达式,然后创建挂起的警报并相应地触发或删除以前挂起的警报。
            vector, err := rule.Eval(ctx, ts, g.opts.QueryFunc, g.opts.ExternalURL)
            .......
			if ar, ok := rule.(*AlertingRule); ok {
				ar.sendAlerts(ctx, ts, g.opts.ResendDelay, g.interval, g.opts.NotifyFunc)
			}
            ......
		}(i, rule)
	}
}

我们仔细看该方法的参数,有一个 g.opts.ResendDelay 参数,看名字,被描述为 重新发送延迟,看名字非常像我们想要找的时间,追踪一下

找到与 评估间隔 相关联的另一个时间

g.opts.ResendDelay 这是属于 ManagerOptions 结构体中的一个属性。

// ./rules/manager.go
type ManagerOptions struct {
	// 其他属性略
	ResendDelay     time.Duration // 这就是要找到时间
}

我们查找一下引用,看看它的值从哪里来的。居然是入口的 main.go 文件!!!ResendDelay 是通过一个值传递的,那么 cfg.resendDelay 又是从哪里来的呢~感觉马上就要找到它了~~

// ./cmd/prometheus/main.go
var(
// 其他略
		ruleManager = rules.NewManager(&rules.ManagerOptions{
			ResendDelay:     time.Duration(cfg.resendDelay),
		})
)

cfg.resendDelay 是在 flagConfig 结构体中,看到这个名字,就像命令行标志。。。。。囧。。

type flagConfig struct {
	// 其他属性略
	resendDelay         model.Duration
}

查看一下这个属性的引用,果然。。。是通过命令行标志传递进来的。。。根据描述,可以看到,这个值表示:向 Alertmanager 重新发送警报前需要等待的最少时间。并且默认值是 1m!!这就是我们文章要找到的值!

总结

至此,分析完毕。。。文章开头想要找到的这个 60s ,就是通过 Prometheus Server 的 rules.alert.resend-delay 这个命令行标志传递进来的,而且默认是 1m

并且,也可以清晰得看到,Prometheus Server 会先进行规则评估,评估完成后,再进行告警发送。而 规则评估 与 告警发送 的行为,都是具有时间间隔得。

后面我们可以继续分析一下,PrometheusServer 在评估规则之后,是如何使用这个 重发送间隔 的。

告警发送的逻辑(告警发送在评估规则之后)

为了继续研究,我们回到 Eval() 方法中,看看这个 sendAlerts(),用来发送告警的方法,到底是什么逻辑。

ar.sendAlerts(ctx, ts, g.opts.ResendDelay, g.interval, g.opts.NotifyFunc)

看发送逻辑之前,还需要看一个地方 ./rules/alerting.go 这个代码中有一个作用在 AlertingRule 结构体上的 Eval() 方法,实现了 Rule 接口。

// resolvedRetention 当一个报警已解决之后会在内存中保持状态,并重复向 Alertmanager 发送告警,直到 resolvedRetention 定义的时间为止。也就是说,告警解决后,依然会在 15 分钟内,持续发送告警。
// 只不过,发送的每个告警的 endsAt 时间都是告警解决的时间。
const resolvedRetention = 15 * time.Minute

// Eval() 方法评估规则表达式,然后创建挂起的警报并相应地触发或删除以前挂起的警报。
func (r *AlertingRule) Eval(ctx context.Context, ts time.Time, query QueryFunc, externalURL *url.URL) (promql.Vector, error) {
	res, err := query(ctx, r.vector.String(), ts)
	if err != nil {
		r.SetHealth(HealthBad)
		r.SetLastError(err)
		return nil, err
	}

	r.mtx.Lock()
	defer r.mtx.Unlock()

	// Create pending alerts for any new vector elements in the alert expression
	// or update the expression value for existing elements.
	resultFPs := map[uint64]struct{}{}

	var vec promql.Vector
	var alerts = make(map[uint64]*Alert, len(res))
	for _, smpl := range res {
		// Provide the alert information to the template.
		l := make(map[string]string, len(smpl.Metric))
		for _, lbl := range smpl.Metric {
			l[lbl.Name] = lbl.Value
		}

		tmplData := template.AlertTemplateData(l, r.externalLabels, r.externalURL, smpl.V)
		// Inject some convenience variables that are easier to remember for users
		// who are not used to Go's templating system.
		defs := []string{
			"{{$labels := .Labels}}",
			"{{$externalLabels := .ExternalLabels}}",
			"{{$externalURL := .ExternalURL}}",
			"{{$value := .Value}}",
		}

		expand := func(text string) string {
			tmpl := template.NewTemplateExpander(
				ctx,
				strings.Join(append(defs, text), ""),
				"__alert_"+r.Name(),
				tmplData,
				model.Time(timestamp.FromTime(ts)),
				template.QueryFunc(query),
				externalURL,
			)
			result, err := tmpl.Expand()
			if err != nil {
				result = fmt.Sprintf("<error expanding template: %s>", err)
				level.Warn(r.logger).Log("msg", "Expanding alert template failed", "err", err, "data", tmplData)
			}
			return result
		}

		lb := labels.NewBuilder(smpl.Metric).Del(labels.MetricName)

		for _, l := range r.labels {
			lb.Set(l.Name, expand(l.Value))
		}
		lb.Set(labels.AlertName, r.Name())

		annotations := make(labels.Labels, 0, len(r.annotations))
		for _, a := range r.annotations {
			annotations = append(annotations, labels.Label{Name: a.Name, Value: expand(a.Value)})
		}

		lbs := lb.Labels()
		h := lbs.Hash()
		resultFPs[h] = struct{}{}

		if _, ok := alerts[h]; ok {
			err = fmt.Errorf("vector contains metrics with the same labelset after applying alert labels")
			// We have already acquired the lock above hence using SetHealth and
			// SetLastError will deadlock.
			r.health = HealthBad
			r.lastError = err
			return nil, err
		}

		alerts[h] = &Alert{
			Labels:      lbs,
			Annotations: annotations,
			ActiveAt:    ts,
			State:       StatePending,
			Value:       smpl.V,
		}
	}

	for h, a := range alerts {
		// Check whether we already have alerting state for the identifying label set.
		// Update the last value and annotations if so, create a new alert entry otherwise.
		if alert, ok := r.active[h]; ok && alert.State != StateInactive {
			alert.Value = a.Value
			alert.Annotations = a.Annotations
			continue
		}

		r.active[h] = a
	}

	// Check if any pending alerts should be removed or fire now. Write out alert timeseries.
	for fp, a := range r.active {
		if _, ok := resultFPs[fp]; !ok {
			// If the alert was previously firing, keep it around for a given
			// retention time so it is reported as resolved to the AlertManager.
			if a.State == StatePending || (!a.ResolvedAt.IsZero() && ts.Sub(a.ResolvedAt) > resolvedRetention) {
				delete(r.active, fp)
			}
			if a.State != StateInactive {
				a.State = StateInactive
				a.ResolvedAt = ts
			}
			continue
		}

		if a.State == StatePending && ts.Sub(a.ActiveAt) >= r.holdDuration {
			a.State = StateFiring
			a.FiredAt = ts
		}

		if r.restored {
			vec = append(vec, r.sample(a, ts))
			vec = append(vec, r.forStateSample(a, ts, float64(a.ActiveAt.Unix())))
		}
	}

	// We have already acquired the lock above hence using SetHealth and SetLastError will deadlock.
	r.health = HealthGood
	r.lastError = err
	return vec, nil
}

这里面有一个常量 resolvedRetention,默认值是 15 分钟,当一个告警解决后,会保留 15 分钟,并且依然会持续发送告警给 Alertmanager,只不过每次发送的内容中的 endsAt(告警结束时间),都是同一个,并且是告警解决的时间。

而 sendAlerts() 函数就很明了,但是我并没有看到计时器相关的代码,只能看到它调用了 resendDelay(重发送延迟) 与 interval(评估间隔) 这两个时间,但是并没有像 run() 方法中,用 interval 生成一个计时器。resendDelay,仅仅传递了个值

// ./rules/alerting.go
func (r *AlertingRule) sendAlerts(ctx context.Context, ts time.Time, resendDelay time.Duration, interval time.Duration, notifyFunc NotifyFunc) {
	alerts := []*Alert{}
	r.ForEachActiveAlert(func(alert *Alert) {
		if alert.needsSending(ts, resendDelay) {
			alert.LastSentAt = ts
			// Allow for two Eval or Alertmanager send failures.
			delta := resendDelay
			if interval > resendDelay {
				delta = interval
			}
			alert.ValidUntil = ts.Add(4 * delta)
			anew := *alert
			alerts = append(alerts, &anew)
		}
	})
	notifyFunc(ctx, r.vector.String(), alerts...)
}

在 needsSending() 方法中,评估间隔 的值 在 大于 resendDelay 的值时,被赋予 delta 变量。resendDelay 被用在了 needsSending() 方法中。

// ./rules/alerting.go
func (a *Alert) needsSending(ts time.Time, resendDelay time.Duration) bool {
	if a.State == StatePending {
		return false
	}
	// 当一个告警在最后一次发送给 Alertmanager 之后,变为已解决,则重新发送该告警
    // 假如解决时间是1点10分,最后发送告警时间是1点,那么就会重新发送告警。只不过此时告警中的 endsAt(结束时间) 是告警真实解决时间。一般情况 endsAt 是预估的时间。
	if a.ResolvedAt.After(a.LastSentAt) {
		return true
	}
	return a.LastSentAt.Add(resendDelay).Before(ts)
}

这里是判断告警状态的,Pending 状态的告警是不发送的。

至此就断了。。。。无法找到 resendDelay 到底是如何让 PrometheusServer 可以每隔该时间,才重新发送告警。

不过结果是好的~至少已经找到了文章开头想要找的时间。

参考

// 组,就是一组规则的集合,这组规则被放在 rules 属性中。集合配置文件可以很容易联想。
// 一个规则组,具有名称、所属文件、规则集合、选项、评估时间 等等属性。所以,评估规则时,是以组为单位进行的。
type Group struct {
	name                 string
	file                 string
	interval             time.Duration // 这就是 评估间隔,在 LoadGroup() 方法中,该值会根据配置文件内容决定。
	rules                []Rule
	seriesInPreviousEval []map[string]labels.Labels // One per Rule.
	staleSeries          []labels.Labels
	opts                 *ManagerOptions
	mtx                  sync.Mutex
	evaluationTime       time.Duration
	lastEvaluation       time.Time

	shouldRestore bool

	markStale   bool
	done        chan struct{}
	terminated  chan struct{}
	managerDone chan struct{}

	logger log.Logger

	metrics *Metrics
}

反向查找

从上面的代码可以看到,评估间隔 被用作 Group 这个结构体的属性了。并使用了 NewGroup() 函数实例化了该结构体,所以配置文件中评估间隔的值,也就被带到了 groups 这个变量中。所以,我们再想追踪评估间隔,就应该开始追踪 groups 这个变量了。咱们先看一下 Group 结构体的属性,详见参考

看看评估间隔被用来做什么了

既然 评估间隔 的值,被用来实例化 Group 结构体,那么接下来,就要看看这个结构体中,评估间隔 这个属性,被用到哪里了。挨个看了一下,sendAlerts() 方法,非常像我们想要找到。

接下来就来研究一下 sendAlerts() 方法,光从名字上看,就很像了。sendAlerts 就是发送告警的意思。sendAlerts 属于 Eval() 方法,作用在 Group 这个结构体上。

规则评估的逻辑

// ./rules/manager.go
// Eval 是规则评估行为的主要逻辑。用于运行一个单一的评估周期,在这个周期中,所有的规则都会被依次评估。
func (g *Group) Eval(ctx context.Context, ts time.Time) {
	for i, rule := range g.rules {
		select {
		case <-g.done:
			return
		default:
		}
		func(i int, rule Rule) {
			if ar, ok := rule.(*AlertingRule); ok {
				ar.sendAlerts(ctx, ts, g.opts.ResendDelay, g.interval, g.opts.NotifyFunc)
			}
		}(i, rule)
	}
}

而 sendAlerts() 函数就很明了了,这里的 评估间隔 是第三个参数,这次终于不再只是传递,而是使用这个值进行计算了。

// ./rules/alerting.go
func (r *AlertingRule) sendAlerts(ctx context.Context, ts time.Time, resendDelay time.Duration, interval time.Duration, notifyFunc NotifyFunc) {
	alerts := []*Alert{}
	r.ForEachActiveAlert(func(alert *Alert) {
		if alert.needsSending(ts, resendDelay) {
			alert.LastSentAt = ts
			// Allow for two Eval or Alertmanager send failures.
			delta := resendDelay
			if interval > resendDelay {
				delta = interval
			}
			alert.ValidUntil = ts.Add(4 * delta)
			anew := *alert
			alerts = append(alerts, &anew)
		}
	})
	notifyFunc(ctx, r.vector.String(), alerts...)
}

规则评估的时间间隔

接下来就是重点了,既然 评估间隔 的值被用在 sendAlerts() 方法中,也就是说,在调用 sendAlerts() 方法之前,需要先确定时间,满足条件之后再执行具体的发送告警的逻辑,这就要看看调用 sendAlerts() 方法的 Eval() 的实现逻辑了。而在仔细观察后,发现,Eval() 就如其注释描述的一样,仅仅负责运行评估逻辑,而不负责处理时间间隔,那么也就是说,这个时间等待的操作,肯定是在调用 Eval() 的地方实现的。我需要再次向上寻找调用 Eval() 的函数逻辑。此时找到了 run() 方法。

// ./rules/manager.go 代码中省略了无关内容
// run 方法就是 Prometheus Server 进行规则管理的主要逻辑。主要的逻辑就是运行 Eval() 来评估规则。
func (g *Group) run(ctx context.Context) {
	// 等待初始数量以保持一致的时段间隔。也就是用来设置一个时间戳,所有的等待时间都以该时间戳作为起点开始计算。
	// g.interval 就是评估间隔,这里仅仅作为一个生成时间戳的参数,无具体意义
	evalTimestamp := g.evalTimestamp().Add(g.interval)
	ctx = promql.NewOriginContext(ctx, map[string]interface{}{
		"ruleGroup": map[string]string{
			"file": g.File(),
			"name": g.Name(),
		},
	})

// Eval() 方法在此被调用,接下来,就要看看 iter() 被谁调用
	iter := func() {

		g.Eval(ctx, evalTimestamp)
	}
	// 使用配置文件中 评估间隔 的值,设置一个 Ticker。这个 tick 变量就是后面死循环会用到的真正的 评估间隔。
	// 这里的假设是由于 TICKER 是在等待`evalTimestamp`通过之后才启动的
	// 因此 tick 将在每次`evalTimestamp + N * g.interval`出现之后立即触发。
	tick := time.NewTicker(g.interval)
	defer tick.Stop()

// 程序运行后,即进行一次评估
	iter()
	// 这里是一个死循环,也调用了评估行为,明显就是我们要找的按照特定时间周期运行的规则评估行为。
	for {
		select {
		case <-g.done:
			return
		default:
			select {
			case <-g.done:
				return
			// 调用 tick.C 每等待一个 评估间隔 的时间,都会执行一次规则评估的行为
			case <-tick.C:
				evalTimestamp = evalTimestamp.Add((missed + 1) * g.interval)
				iter()
			}
		}
	}
}

上面代码的 for{} 循环 部分,就是 Prometheus Server 会持续评估规则的主要逻辑

告警发送的逻辑(告警发送在评估规则之后)

现在确认了 评估间隔 的行为,但是依然无法确定文章开头提到的,除了 评估间隔 的时间以外,另一个促使发送告警产生间隔的时间是什么,这时,我们回到 sendAlerts() 方法中,这里面还调用了 g.opts.ResendDelay 这个值,那么,会不会是这个值影响的呢?~

找到与 评估间隔 相关联的另一个时间

在 sendAlerts() 方法中,评估间隔 的值 在 大于 resendDelay 的值时,被赋予 delta 变量。

// ./rules/alerting.go
func (r *AlertingRule) sendAlerts(ctx context.Context, ts time.Time, resendDelay time.Duration, interval time.Duration, notifyFunc NotifyFunc) {
	alerts := []*Alert{}
	r.ForEachActiveAlert(func(alert *Alert) {
		if alert.needsSending(ts, resendDelay) {
			alert.LastSentAt = ts
			// Allow for two Eval or Alertmanager send failures.
			delta := resendDelay
			if interval > resendDelay {
				delta = interval
			}
			alert.ValidUntil = ts.Add(4 * delta)
			anew := *alert
			alerts = append(alerts, &anew)
		}
	})
	notifyFunc(ctx, r.vector.String(), alerts...)
}

先说结论,事实上,resendDelay 就是我们文章开头要找到的 60 秒 时间。继续分析,我们通过 resendDelay 来看看它到底是个什么,resendDelay 被用在了 needsSending() 方法中。

// ./rules/alerting.go
func (a *Alert) needsSending(ts time.Time, resendDelay time.Duration) bool {
	if a.State == StatePending {
		return false
	}
	// if an alert has been resolved since the last send, resend it
	if a.ResolvedAt.After(a.LastSentAt) {
		return true
	}
	return a.LastSentAt.Add(resendDelay).Before(ts)
}

接下来让我们看看 resendDelay,通过调用 sendAlerts() 找到,调用它时传递的 g.opts.REsendDelay 参数,这是属于 ManagerOptions 结构体中的一个属性。

// ./rules/manager.go
type ManagerOptions struct {
	// 其他属性略
	ResendDelay     time.Duration
 // 这就是要找到时间
}

我们查找一下引用,看看它的值从哪里来的。居然是入口的 main.go 文件!!!ResendDelay 是通过一个值传递的,那么 cfg.resendDelay 又是从哪里来的呢~感觉马上就要找到它了~~

// ./cmd/prometheus/main.go
var(
// 其他略
		ruleManager = rules.NewManager(&rules.ManagerOptions{
			ResendDelay:     time.Duration(cfg.resendDelay),
		})
)

cfg.resendDelay 是在 flagConfig 结构体中,看到这个名字,就像命令行标志。。。。。囧。。

type flagConfig struct {
	// 其他属性略
	resendDelay         model.Duration
}

查看一下这个属性的引用,果然。。。是通过命令行标志传递进来的。。。


最后修改 July 23, 2024: clearup observability (4b9f5578)