Prometheus源码专题【左扬精讲】—— 监控系统 Prometheus 3.4.0 源码解析:Alerting rule 告警规则
https://github.com/prometheus/prometheus/blob/v3.4.0/rules/alerting.go
在 Prometheus 的监控体系中,规则系统是实现指标预处理与告警能力的核心组件。本文将聚焦于 Alerting Rule(告警规则),从源码角度深入解析其与 Recording Rule 的区别、定义方式、执行流程、状态转换机制、通知重发策略及样本存储逻辑。
一、Alerting Rule vs Recording Rule:核心区别
Prometheus 中的规则分为两类:Recording Rule(记录规则) 和 Alerting Rule(告警规则),二者均基于 PromQL 表达式,但定位与用途截然不同。
| 维度 | Recording Rule | Alerting Rule |
| 核心用途 |
预计算复杂 PromQL 结果,优化查询性能 |
基于指标阈值触发告警,驱动通知流程 |
| 输出产物 |
新的时间序列(存储于 Prometheus 中) |
告警状态(Pending/Firing/Inactive)及通知 |
| 配置关键字 |
record 定义生成的指标名 |
alert 定义告警名称 |
| 核心字段 |
无状态相关字段,仅包含 expr 和 labels |
包含 for( pending 时长)、keep_firing_for(持续 firing 时长)等状态控制字段 |
| 源码对应结构体 |
RecordingRule (简化版,无状态管理) |
AlertingRule (复杂状态机,含 active 告警跟踪) |
从源码角度看,两者均实现了 Rule 接口,但 AlertingRule 额外包含了告警状态管理、通知发送、样本生成等复杂逻辑(集中在 rules/alerting.go 中),而 RecordingRule 仅关注表达式计算与结果存储。
二、Alerting Rule 的定义
2.1、配置层面定义
Alerting Rule 通过 YAML 配置文件定义,核心结构如下(源自官方文档):
groups:
- name: example
rules:
- alert: HighRequestLatency # 告警名称
expr: job:request_latency_seconds:mean5m{job="myjob"} > 0.5 # 触发条件(PromQL)
for: 10m # 持续满足条件多久后从 Pending 转为 Firing
keep_firing_for: 5m # 条件不满足后,保持 Firing 状态的时长
labels: # 附加到告警的标签(可覆盖冲突标签)
severity: page
annotations: # 告警描述信息(支持模板)
summary: High request latency
2.2、源码层面结构
在 https://github.com/prometheus/prometheus/blob/v3.4.0/rules/alerting.go#L108 中,AlertingRule 结构体是告警规则的核心载体,关键字段如下:
// AlertingRule 从其向量表达式生成告警
type AlertingRule struct {
// 告警的名称
name string
// 用于生成告警的向量表达式
vector parser.Expr
// 在告警从未决状态转换为触发状态之前,标签集需要在表达式输出向量中持续的时间
holdDuration time.Duration
// 告警在问题解决后应保持触发状态的时间量
keepFiringFor time.Duration
// 附加到生成的告警样本向量的额外标签
labels labels.Labels
// 非标识性的键/值对
annotations labels.Labels
// 来自全局配置的外部标签
externalLabels map[string]string
// 来自 --web.external-url 标志的外部 URL
externalURL string
// 如果旧状态已被恢复,则为 true。我们仅在恢复后才开始为 ALERT_FOR_STATE 持久化样本
restored *atomic.Bool
// 评估规则所花费的时间(秒)
evaluationDuration *atomic.Duration
// 规则最后一次评估的时间戳
evaluationTimestamp *atomic.Time
// 告警规则的健康状态
health *atomic.String
// 告警规则遇到的最后一个错误
lastError *atomic.Error
// 保护 `active` 映射的互斥锁
activeMtx sync.Mutex
// 当前活跃(未决或触发)的告警映射,由它们对应的标签集的指纹作为键
active map[uint64]*Alert
logger *slog.Logger
dependenciesMutex sync.RWMutex
dependentRules []Rule
dependencyRules []Rule
}
其中,active 字段是状态管理的核心,通过标签集的指纹(uint64)映射到具体的 Alert 实例,跟踪每个标签组合的告警状态。
告警规则的执行由 Rule Manager 驱动,定期(按 evaluation_interval 配置)在规则组(Group)中批量执行。核心流程在 Group.Eval 方法(https://github.com/prometheus/prometheus/blob/v3.4.0/rules/group.go)中实现,针对 AlertingRule 的关键步骤如下:
-
- 表达式计算:调用 AlertingRule.Eval 方法,执行 vector 字段对应的 PromQL 表达式,获取当前满足条件的样本集合。
- 状态更新:对比当前样本与 active 中记录的历史告警,更新每个标签集的状态(Pending/Firing/Inactive)。
- 样本生成:生成 ALERTS 和 ALERTS_FOR_STATE 指标样本(用于跟踪告警状态)。
- 通知发送:调用 sendAlerts 方法,根据状态变化触发告警通知(或重发)。
- 结果存储:将生成的样本写入 Prometheus 存储(通过 Appender 接口)。
四、告警状态转换:从 Inactive 到 Firing 的生命周期
Prometheus 告警存在三种状态,定义于 https://github.com/prometheus/prometheus/blob/v3.4.0/rules/alerting.go 中:
type AlertState int
const (
StateInactive AlertState = iota // 非活跃:不满足条件,或已恢复
StatePending // 待触发:满足条件但未达到 for 时长
StateFiring // 触发中:满足条件且超过 for 时长
)
4.1、状态转换逻辑
- Inactive → Pending:当 PromQL 表达式首次返回某标签集时,若规则配置了 for,则该标签集进入 Pending 状态,记录 ActiveAt 时间戳。
- Pending → Firing:若标签集在 Pending 状态持续时间超过 holdDuration(即 当前评估时间 - ActiveAt ≥ holdDuration),则转为 Firing 状态,记录 FiredAt 时间戳。
- Firing/Pending → Inactive:
-
-
- 若表达式不再返回该标签集,且未配置 keepFiringFor,则直接转为 Inactive,记录 ResolvedAt。
- 若配置了 keepFiringFor,则保持 Firing 状态直至 当前评估时间 - ResolvedAt ≥ keepFiringFor,再转为 Inactive。
4.2、影响状态转换的关键字段
-
- holdDuration(for):控制从 Pending 到 Firing 的延迟,避免瞬时波动触发告警。
- keepFiringFor:控制条件不满足后 Firing 状态的延续时间,防止告警 “抖动”(如短暂数据缺失导致误判恢复)。
- ActiveAt/FiredAt/ResolvedAt:Alert 结构体中的时间戳字段,用于计算状态持续时长。
五、告警通知与重发机制
告警触发后,Prometheus 会将通知发送给 Alertmanager,核心逻辑在 AlertingRule.sendAlerts 方法(https://github.com/prometheus/prometheus/blob/v3.4.0/rules/alerting.go#L593)中。
5.1、通知触发条件
needsSending 方法定义了通知发送的判断逻辑:
func (a *Alert) needsSending(ts time.Time, resendDelay time.Duration) bool {
if a.State == StatePending { // Pending 状态不发送通知
return false
}
// 若告警已恢复(ResolvedAt 在 LastSentAt 之后),需发送恢复通知
if a.ResolvedAt.After(a.LastSentAt) {
return true
}
// 若距离上次发送已超过重发间隔,需重发
return a.LastSentAt.Add(resendDelay).Before(ts)
}
needsSending 方法定义了通知是否需要发送的核心判断逻辑,仅当满足以下条件之一时,才会触发通知请求: