凌晨三点,你的手机震动了。PagerDuty 推送了一条 P2 告警:“订单服务 P99 延迟超过 500ms”。你从床上爬起来,打开笔记本电脑,登录监控面板,花了十五分钟排查——发现是一次定时任务导致的短暂毛刺,延迟在告警触发后三分钟就自动恢复了。你把告警标记为”已解决”,回到床上,刚睡着,四点二十又来了一条:“磁盘使用率超过 80%”。你再次起身,发现这台机器一个月前就该下线了,容量早就规划好了,不需要任何操作。
这不是偶然事件。这是绝大多数 On-Call 工程师的日常。
PagerDuty 在 2020 年发布的《State of Digital Operations》报告中给出了一个令人震惊的数据:企业平均每天收到的告警中,超过 50% 是噪声——不需要任何人类操作的告警。Google 的 SRE 团队在内部调研中发现,在引入基于 SLO(Service Level Objective)的告警体系之前,他们的某些核心服务每周产生超过 200 条告警,其中只有不到 15% 最终导致了实际的工程操作。
告警系统的目的是在用户受到影响之前(或刚开始受到影响时)通知工程师采取行动。但大多数告警系统做的事情恰恰相反——它们在用户根本没有受到影响时制造噪声,在用户真正受到影响时因为工程师已经对告警麻木了而错过关键信号。
这就是经典的”狼来了”问题。
本文要回答一个核心问题:如何设计一套告警策略,让每一条告警都是可操作的(Actionable),每一次 On-Call 被叫醒都是值得的?
一、为什么基于阈值的告警注定失败
1.1 阈值告警的直觉陷阱
大多数告警规则长这样:
# 典型的阈值告警规则
- alert: HighLatency
expr: histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m])) > 0.5
for: 5m
labels:
severity: warning
annotations:
summary: "P99 延迟超过 500ms"看起来很合理——“P99 延迟超过 500ms 就告警”。但这条规则至少有三个根本性问题。
问题一:阈值与业务影响脱节。 500ms 这个数字从哪里来的?大多数情况下,是工程师凭经验拍脑袋定的。它和用户体验之间没有量化的关联。也许 P99 到 800ms 用户才会有感知,也许某些接口 200ms 就已经不可接受了。固定阈值无法区分这些差异。
问题二:无法区分毛刺和趋势。
for: 5m
的持续时间要求只能做到最粗糙的过滤。一个 6
分钟的延迟毛刺会触发告警,即使它在第 7
分钟就完全恢复了。而一个缓慢但持续的性能退化——每天 P99 增加
10ms——可能在两个月后才触及阈值,但到那时问题已经积累到很难修复。
问题三:阈值数量爆炸。 一个中等规模的微服务架构有 50 个服务,每个服务至少需要监控延迟、错误率、吞吐量、饱和度四个黄金信号(Golden Signals),再加上每个服务特有的业务指标。保守估计需要 300 条以上的阈值告警规则。这些阈值谁来维护?服务扩容后阈值要不要调?流量模式变化后呢?
1.2 告警疲劳的恶性循环
阈值设低了,误报(False Positive)泛滥。阈值设高了,漏报(False Negative)增加。大多数团队的应对策略是:先设低一点,宁可多报不漏报。
这直接导致了告警疲劳(Alert Fatigue)。
告警疲劳不是一个感性描述,它有严格的医学和工业研究支撑。美国急诊医学协会(ACEP)的研究表明,当告警系统的误报率超过 40% 时,操作人员对告警的响应率开始显著下降。当误报率超过 70% 时,操作人员几乎会忽略所有告警——包括真正的紧急告警。
在软件工程领域,PagerDuty 的数据佐证了同样的规律:
| 指标 | 健康团队 | 疲劳团队 |
|---|---|---|
| 每周人均告警数 | 少于 10 条 | 超过 40 条 |
| 告警确认中位时间 | 3 分钟 | 超过 20 分钟 |
| 误报比例 | 低于 20% | 超过 60% |
| 可操作告警比例 | 超过 80% | 低于 30% |
| 人员流失率 | 基准水平 | 2-3 倍于基准 |
告警疲劳的恶性循环是这样的:
- 阈值告警产生大量误报
- 工程师开始忽略告警或延迟响应
- 真正的故障被淹没在噪声中,平均恢复时间(MTTR)增加
- 管理层要求”加强监控”,增加更多告警规则
- 误报进一步增加,回到步骤 1
1.3 阈值告警的数学困境
从统计学角度看,阈值告警本质上是一个二分类器(Binary Classifier):高于阈值为”异常”,低于阈值为”正常”。这个分类器的性能取决于阈值的选择。
假设一个服务的 P99 延迟在正常情况下服从均值为 200ms、标准差为 50ms 的正态分布。如果阈值设在 300ms(均值 + 2 个标准差),那么在完全正常的情况下仍然有约 2.3% 的时间窗口会触发告警。按 5 分钟一个窗口、一天 288 个窗口计算,平均每天会产生约 6.6 次误报。一个月就是近 200 次。
即使加上 for: 5m
的持续时间要求,也只是减少了短暂毛刺的误报,无法解决底层的统计问题:固定阈值无法适应时变的流量模式。工作日白天的延迟分布和凌晨的延迟分布完全不同,周末和工作日也不同,大促期间更是另一个世界。
这就是为什么我们需要一种完全不同的告警思路。
二、SLO:从”指标异常”到”用户受损”
2.1 SLO 的核心思想
SLO(服务等级目标,Service Level Objective)是一种用量化方式定义”用户可接受的服务质量下限”的方法。它不关心任何单一指标是否”异常”,只关心一个问题:用户的体验是否在可接受的范围内?
一个典型的 SLO 定义如下:
在过去 30 天内,99.9% 的 HTTP 请求应在 300ms 内成功返回(状态码 2xx 或 3xx)。
这个定义包含三个要素:
- SLI(服务等级指标,Service Level Indicator):HTTP 请求的成功率和延迟,这是实际可测量的指标。
- 目标值:99.9%,这是团队与业务方协商确定的质量下限。
- 时间窗口:30 天,这是评估周期。
SLO 的核心转变在于:它把告警的触发条件从”某个指标超过某个阈值”变成了”用户的错误预算(Error Budget)正在被消耗得太快”。
2.2 错误预算的概念
如果 SLO 是 99.9%,那么在 30 天的评估窗口内,允许的”坏事件”比例是 0.1%。这 0.1% 就是错误预算(Error Budget)。
具体来说:
- 30 天 = 43200 分钟
- 错误预算 = 43200 x 0.1% = 43.2 分钟
这意味着在 30 天内,服务最多允许有 43.2 分钟的”不合规”时间。如果在前 10 天就用掉了 40 分钟的预算,那剩下 20 天只剩 3.2 分钟的余量——这是一个非常危险的状态,需要立即采取行动(比如冻结变更、回滚最近的部署)。
错误预算把一个模糊的质量问题变成了一个精确的数学问题:
错误预算剩余 = 总预算 - 已消耗预算
= (1 - SLO目标) x 窗口时长 - 已发生的坏事件时长
2.3 从错误预算到燃烧率
仅仅知道”错误预算还剩多少”是不够的。我们需要知道的是:错误预算正在以多快的速度被消耗?
这就引出了燃烧率(Burn Rate)的概念。
燃烧率的定义很直观:
燃烧率 = 实际错误率 / SLO 允许的错误率
如果 SLO 允许的错误率是 0.1%,当前实际错误率是 0.1%,那么燃烧率 = 1。这意味着错误预算正在以”正好用完”的速度被消耗——30 天后刚好用完全部预算。
如果实际错误率是 1%,那么燃烧率 = 10。这意味着错误预算正在以 10 倍的速度被消耗——3 天就会用完全部 30 天的预算。
下表展示了不同燃烧率下的预算耗尽时间:
| 燃烧率 | 实际错误率(SLO=99.9%) | 预算耗尽时间 | 严重程度 |
|---|---|---|---|
| 1 | 0.1% | 30 天 | 正常 |
| 2 | 0.2% | 15 天 | 需关注 |
| 6 | 0.6% | 5 天 | 需要干预 |
| 14.4 | 1.44% | 约 50 小时 | 紧急 |
| 36 | 3.6% | 约 20 小时 | 严重 |
| 720 | 72% | 1 小时 | 全面故障 |
燃烧率的优雅之处在于:它天然地将告警的严重程度与业务影响绑定在一起。燃烧率为 1 的时候不需要任何操作——这是正常状态。燃烧率越高,预算耗尽得越快,越需要紧急响应。
三、多窗口燃烧率告警算法
3.1 单窗口燃烧率的局限
最简单的燃烧率告警是:当过去 N 分钟的燃烧率超过阈值时触发告警。例如:
告警条件:过去 1 小时的燃烧率 > 14.4
这条规则的含义是:如果当前的错误率持续下去,将在大约 50 小时(约 2 天)内耗尽全部 30 天的错误预算。这是一个合理的 P1 告警触发条件。
但单窗口有两个问题:
窗口太短,容易误报。 如果用 5 分钟窗口,一次短暂的上游超时可能导致燃烧率瞬间飙升到 100,触发紧急告警。但实际上这次超时在 2 分钟内就恢复了,对总的错误预算影响微乎其微。
窗口太长,反应太慢。 如果用 24 小时窗口,一次持续 30 分钟的严重故障(比如 50% 的请求失败)被稀释在 24 小时的大量正常请求中,燃烧率可能只显示为 3 或 4,远低于告警阈值。等到窗口的平均值足够高时,故障可能已经持续了好几个小时。
3.2 多窗口多燃烧率算法
Google SRE 团队在《Site Reliability Workbook》中提出了多窗口多燃烧率(Multi-Window Multi-Burn-Rate)告警算法。核心思想是:用长窗口确保告警的显著性(Significance),用短窗口确保问题仍在发生(Recency)。
具体来说,每条告警规则包含两个窗口:
- 长窗口:用于判断是否消耗了足够多的错误预算,避免对小毛刺过度反应。
- 短窗口:通常是长窗口的 1/12,用于确认问题当前仍在发生,避免对已恢复的问题持续告警。
告警仅在两个窗口同时满足条件时才触发:
告警触发条件 =
长窗口燃烧率 > 阈值 AND 短窗口燃烧率 > 阈值
Google SRE 推荐的标准配置如下:
| 严重程度 | 长窗口 | 短窗口 | 燃烧率阈值 | 消耗预算比例 | 响应要求 |
|---|---|---|---|---|---|
| P1(紧急) | 1 小时 | 5 分钟 | 14.4 | 30 天预算的 2% | 立即响应 |
| P2(高) | 6 小时 | 30 分钟 | 6 | 30 天预算的 5% | 30 分钟内响应 |
| P3(中) | 3 天 | 6 小时 | 1 | 30 天预算的 10% | 工作时间处理 |
3.3 数学推导
为什么 P1 的燃烧率阈值是 14.4?这个数字不是拍脑袋定的,而是从预算消耗比例反推出来的。
P1 告警的设计目标是:在 1 小时内消耗掉 30 天错误预算的 2%。
1 小时消耗 2% 的 30 天预算
= 在 1 小时内消耗了 0.02 x 30 天 = 0.6 天的预算
燃烧率 = 0.6 天 / (1 小时 / 24 小时/天)
= 0.6 / (1/24)
= 0.6 x 24
= 14.4
同理,P2 告警:在 6 小时内消耗 5% 的 30 天预算。
燃烧率 = (0.05 x 30) / (6/24)
= 1.5 / 0.25
= 6
P3 告警:在 3 天内消耗 10% 的 30 天预算。
燃烧率 = (0.10 x 30) / 3
= 3 / 3
= 1
3.4 Prometheus 告警规则实现
以下是一个完整的多窗口燃烧率告警规则在 Prometheus 中的实现。假设 SLO 为 99.9%(错误预算 = 0.1%):
首先,定义 SLI 的记录规则(Recording Rules),预先计算不同窗口的错误率:
# recording-rules.yaml
groups:
- name: slo-error-rate
interval: 30s
rules:
# 5 分钟窗口错误率
- record: slo:http_error_rate:ratio_rate5m
expr: |
sum(rate(http_requests_total{status=~"5.."}[5m]))
/
sum(rate(http_requests_total[5m]))
# 30 分钟窗口错误率
- record: slo:http_error_rate:ratio_rate30m
expr: |
sum(rate(http_requests_total{status=~"5.."}[30m]))
/
sum(rate(http_requests_total[30m]))
# 1 小时窗口错误率
- record: slo:http_error_rate:ratio_rate1h
expr: |
sum(rate(http_requests_total{status=~"5.."}[1h]))
/
sum(rate(http_requests_total[1h]))
# 6 小时窗口错误率
- record: slo:http_error_rate:ratio_rate6h
expr: |
sum(rate(http_requests_total{status=~"5.."}[6h]))
/
sum(rate(http_requests_total[6h]))
# 3 天窗口错误率
- record: slo:http_error_rate:ratio_rate3d
expr: |
sum(rate(http_requests_total{status=~"5.."}[3d]))
/
sum(rate(http_requests_total[3d]))然后,定义多窗口燃烧率告警规则:
# alerting-rules.yaml
groups:
- name: slo-burn-rate-alerts
rules:
# P1:1 小时长窗口 + 5 分钟短窗口,燃烧率 > 14.4
- alert: SLOBurnRateCritical
expr: |
(
slo:http_error_rate:ratio_rate1h > (14.4 * 0.001)
and
slo:http_error_rate:ratio_rate5m > (14.4 * 0.001)
)
for: 2m
labels:
severity: critical
slo: "availability-99.9"
window: "1h"
burn_rate: "14.4"
annotations:
summary: "SLO 错误预算正在以 14.4 倍速率消耗"
description: |
当前 1h 错误率 {{ $value | humanizePercentage }},
预计 {{ printf "%.0f" (div 720 14.4) }} 小时内耗尽 30 天预算。
Runbook: https://wiki.internal/runbooks/slo-burn-rate-critical
dashboard: "https://grafana.internal/d/slo-overview"
# P2:6 小时长窗口 + 30 分钟短窗口,燃烧率 > 6
- alert: SLOBurnRateHigh
expr: |
(
slo:http_error_rate:ratio_rate6h > (6 * 0.001)
and
slo:http_error_rate:ratio_rate30m > (6 * 0.001)
)
for: 5m
labels:
severity: warning
slo: "availability-99.9"
window: "6h"
burn_rate: "6"
annotations:
summary: "SLO 错误预算正在以 6 倍速率消耗"
description: |
当前 6h 错误率 {{ $value | humanizePercentage }},
预计 {{ printf "%.0f" (div 720 6) }} 小时内耗尽 30 天预算。
Runbook: https://wiki.internal/runbooks/slo-burn-rate-high
# P3:3 天长窗口 + 6 小时短窗口,燃烧率 > 1
- alert: SLOBurnRateLow
expr: |
(
slo:http_error_rate:ratio_rate3d > (1 * 0.001)
and
slo:http_error_rate:ratio_rate6h > (1 * 0.001)
)
for: 30m
labels:
severity: info
slo: "availability-99.9"
window: "3d"
burn_rate: "1"
annotations:
summary: "SLO 错误预算正在以正常速率消耗,但已持续较长时间"
description: |
当前 3d 错误率 {{ $value | humanizePercentage }},
需在工作时间排查。
Runbook: https://wiki.internal/runbooks/slo-burn-rate-low3.5 多窗口机制的告警行为分析
为什么这种双窗口机制能同时解决误报和漏报问题?用三个场景来说明。
场景一:2 分钟的短暂毛刺,100% 错误率。
- 5 分钟短窗口:燃烧率 = (2/5) / 0.001 = 400。超过 14.4 阈值。
- 1 小时长窗口:燃烧率 = (2/60) / 0.001 = 33.3。超过 14.4 阈值。
- 但
for: 2m要求条件持续 2 分钟。毛刺在 2 分钟内结束后,5 分钟短窗口的燃烧率迅速下降。告警大概率不会触发。 - 即使触发了,3 分钟后短窗口燃烧率降到阈值以下,告警自动恢复。
场景二:持续 30 分钟的中等故障,10% 错误率。
- 5 分钟短窗口:燃烧率 = 0.10 / 0.001 = 100。超过阈值。
- 1 小时长窗口:30 分钟后,燃烧率 = (30/60 x 0.10) / 0.001 = 50。超过阈值。
- 两个窗口同时满足,P1 告警在故障开始后约 2 分钟内触发。
场景三:持续 3 天的缓慢退化,错误率从 0.05% 逐步上升到 0.15%。
- 1 小时窗口和 6 小时窗口的燃烧率可能在 1-3 之间波动,低于 P1 和 P2 阈值。
- 3 天长窗口的平均燃烧率逐步超过 1,6 小时短窗口确认趋势仍在持续。
- P3 告警在退化开始后的 1-2 天内触发,提醒团队在工作时间排查。
这就是多窗口多燃烧率的核心价值:不同严重程度的问题由不同的窗口组合捕获,每种问题都在恰当的时间被发现。
四、告警流水线:从指标到通知
4.1 告警的完整生命周期
一条告警从产生到被人看到,经过以下阶段:
flowchart LR
A["指标采集<br>Prometheus/VictoriaMetrics"] --> B["规则评估<br>Alerting Rules"]
B --> C{"触发条件<br>是否满足?"}
C -->|否| D["无操作"]
C -->|是| E["等待 for 持续时间"]
E --> F{"仍然满足?"}
F -->|否| D
F -->|是| G["发送到 Alertmanager"]
G --> H["去重<br>Deduplication"]
H --> I["分组<br>Grouping"]
I --> J["路由<br>Routing"]
J --> K["抑制检查<br>Inhibition"]
K --> L["静默检查<br>Silencing"]
L --> M{"应发送?"}
M -->|否| D
M -->|是| N["通知<br>Notification"]
N --> O["PagerDuty / Slack<br>/ Email / Webhook"]
这个流水线中,Prometheus 负责前半段(指标采集和规则评估),Alertmanager 负责后半段(去重、分组、路由、抑制、静默和通知)。两者的职责清晰分离:Prometheus 回答”什么出了问题”,Alertmanager 回答”通知谁、通过什么渠道、是否应该通知”。
4.2 为什么需要 Alertmanager
为什么不能让 Prometheus 直接发通知?因为在生产环境中,告警的通知逻辑比告警的触发逻辑复杂得多:
- 去重(Deduplication):多个 Prometheus 实例可能对同一条规则独立求值,产生重复告警。Alertmanager 基于标签指纹去重,确保同一条告警只通知一次。
- 分组(Grouping):一次数据库故障可能同时触发 20 个依赖服务的告警。Alertmanager 可以把这些相关告警合并为一条通知,避免 On-Call 工程师收到 20 条独立的消息。
- 路由(Routing):不同团队负责不同服务,不同严重级别走不同通知渠道。Alertmanager 的路由树(Routing Tree)实现了灵活的告警分发。
- 抑制(Inhibition):当数据库集群整体故障时,不需要再通知每个依赖服务的错误率上升。高级别告警可以抑制低级别告警。
- 静默(Silencing):维护窗口期间临时屏蔽特定告警。
五、Alertmanager 配置详解
5.1 路由树(Routing Tree)
Alertmanager 的路由配置是一棵树状结构。每条告警从根节点开始匹配,沿着树向下走,直到找到匹配的叶节点。以下是一个完整的生产级配置示例:
# alertmanager.yaml
global:
resolve_timeout: 5m
smtp_from: "alertmanager@company.com"
smtp_smarthost: "smtp.company.com:587"
smtp_auth_username: "alertmanager"
smtp_auth_password_file: "/etc/alertmanager/smtp_password"
pagerduty_url: "https://events.pagerduty.com/v2/enqueue"
# 通知模板
templates:
- "/etc/alertmanager/templates/*.tmpl"
# 路由树
route:
# 默认接收者
receiver: "platform-slack"
# 分组键:相同 alertname 和 service 的告警合并为一条通知
group_by: ["alertname", "service"]
# 首次分组等待时间:等待 30 秒收集同组的其他告警
group_wait: 30s
# 同组告警的后续通知间隔
group_interval: 5m
# 已发送告警的重复通知间隔
repeat_interval: 4h
# 子路由
routes:
# 关键服务的紧急告警 -> PagerDuty
- match:
severity: critical
receiver: "pagerduty-critical"
group_wait: 10s
repeat_interval: 1h
# 继续匹配子路由(同时发送到多个渠道)
continue: true
# 关键服务的紧急告警同时发到 Slack 的紧急频道
- match:
severity: critical
receiver: "slack-critical"
# 数据库团队的告警
- match_re:
service: "^(mysql|postgresql|redis|mongodb).*"
receiver: "db-team-slack"
routes:
- match:
severity: critical
receiver: "db-team-pagerduty"
# 支付服务的告警 -> 独立的 On-Call 轮换
- match:
service: "payment"
receiver: "payment-team-pagerduty"
group_by: ["alertname", "service", "environment"]
# 非紧急告警 -> 只发 Slack
- match:
severity: info
receiver: "platform-slack"
repeat_interval: 24h
# 接收者定义
receivers:
- name: "pagerduty-critical"
pagerduty_configs:
- service_key_file: "/etc/alertmanager/pagerduty_service_key"
severity: critical
description: '{{ .CommonAnnotations.summary }}'
details:
firing: '{{ .Alerts.Firing | len }}'
dashboard: '{{ (index .Alerts 0).Annotations.dashboard }}'
runbook: '{{ (index .Alerts 0).Annotations.runbook }}'
- name: "slack-critical"
slack_configs:
- api_url_file: "/etc/alertmanager/slack_webhook_url"
channel: "#incidents"
title: '[{{ .Status | toUpper }}] {{ .CommonLabels.alertname }}'
text: >-
{{ range .Alerts }}
*告警:* {{ .Annotations.summary }}
*详情:* {{ .Annotations.description }}
*面板:* {{ .Annotations.dashboard }}
{{ end }}
send_resolved: true
- name: "db-team-slack"
slack_configs:
- api_url_file: "/etc/alertmanager/slack_webhook_url"
channel: "#db-alerts"
send_resolved: true
- name: "db-team-pagerduty"
pagerduty_configs:
- service_key_file: "/etc/alertmanager/pagerduty_db_key"
- name: "payment-team-pagerduty"
pagerduty_configs:
- service_key_file: "/etc/alertmanager/pagerduty_payment_key"
- name: "platform-slack"
slack_configs:
- api_url_file: "/etc/alertmanager/slack_webhook_url"
channel: "#platform-alerts"
send_resolved: true5.2 抑制规则(Inhibition Rules)
抑制规则用于避免告警级联。当一个高级别告警已经触发时,自动抑制相关的低级别告警:
# alertmanager.yaml(续)
inhibit_rules:
# 当集群级别的 critical 告警触发时,抑制同集群的 warning 和 info 告警
- source_match:
severity: critical
target_match_re:
severity: "warning|info"
equal: ["cluster", "namespace"]
# 当节点宕机告警触发时,抑制该节点上所有 Pod 的告警
- source_match:
alertname: NodeDown
target_match_re:
alertname: "Pod.*|Container.*"
equal: ["node"]
# 当数据库主库故障告警触发时,抑制所有依赖该数据库的服务告警
- source_match:
alertname: DatabasePrimaryDown
target_match_re:
alertname: ".*ErrorRate.*|.*Latency.*"
equal: ["database_cluster"]抑制规则的设计原则是:上游故障产生的告警应该抑制下游级联产生的告警。工程师只需要看到根因告警,而不是根因加上几十个症状告警。
5.3 分组策略
分组(Grouping)是减少告警噪声最直接有效的手段之一。好的分组策略可以把 50 条独立的告警合并为 3-5 条有意义的通知。
route:
# 按 alertname 和 service 分组
group_by: ["alertname", "service"]
group_wait: 30s
group_interval: 5mgroup_wait 参数至关重要:它定义了
Alertmanager
在收到第一条告警后等待多久再发送通知。在这个等待时间内,所有匹配同一分组键的告警会被合并到一条通知中。
一个常见的错误是把 group_wait
设得太短。如果设为 0 秒,那么一次数据库故障触发的 20
个服务的告警会分别在不同时刻到达
Alertmanager,每条都立即发送,工程师收到 20 条独立通知。设为
30
秒后,这些告警会被合并为一条通知,里面列出所有受影响的服务。
但也不能设得太长——对于 critical 级别的告警,30
秒的额外延迟可能是不可接受的。因此在上面的配置中,critical
路由的 group_wait 被覆盖为 10 秒。
5.4 静默(Silencing)
静默用于临时屏蔽特定告警,典型场景是计划内维护窗口(Maintenance Window)。
通过 Alertmanager 的 API 或 Web UI 创建静默:
# 创建一个持续 2 小时的静默,屏蔽 payment 服务的所有非 critical 告警
amtool silence add \
--alertmanager.url=http://alertmanager:9093 \
--author="oncall-engineer" \
--comment="Payment service scheduled maintenance window" \
--duration=2h \
service="payment" severity=~"warning|info"静默的最佳实践:
- 每个静默必须有明确的到期时间,禁止创建无限期静默
- 每个静默必须附带创建原因(
--comment) - 定期审计活跃的静默列表,清理过期但未自动删除的静默
- critical 级别的告警原则上不应被静默——如果维护期间可能触发 critical 告警,说明维护方案有风险
六、PagerDuty 的告警疲劳研究
6.1 告警疲劳的定量研究
PagerDuty 的数据科学团队对超过 13000 个团队的告警数据进行了分析,发表了多项关于告警疲劳(Alert Fatigue)的研究成果。以下是核心发现。
发现一:告警数量与响应质量呈非线性负相关。 当每周人均告警数从 5 条增加到 20 条时,平均确认时间增加了 2 倍。当从 20 条增加到 40 条时,确认时间增加了 5 倍。曲线不是线性的——存在一个”悬崖效应”(Cliff Effect),一旦超过某个阈值,响应质量急剧下降。
发现二:凌晨告警的代价远高于白天。 凌晨 2 点被叫醒的认知恢复时间平均需要 25-30 分钟,即使告警是误报。一周超过两次凌晨被叫醒的工程师,第二周的工作效率平均下降 15-20%。
发现三:单一工程师连续 On-Call 超过 7 天,响应质量显著退化。 PagerDuty 建议的 On-Call 轮换周期是 3-5 天,而不是传统的一周。
6.2 告警分级与升级策略
PagerDuty 的告警升级策略(Escalation Policy)是一个多层级的通知机制:
Level 1: 通知主 On-Call 工程师
|
v(5 分钟未确认)
Level 2: 通知副 On-Call 工程师
|
v(10 分钟未确认)
Level 3: 通知团队技术负责人
|
v(15 分钟未确认)
Level 4: 通知部门经理 + 启动 Incident 流程
升级策略的设计要点:
- 每一级都有明确的超时时间。 主 On-Call 5 分钟不确认就自动升级,不要等 30 分钟——5 分钟足够一个醒着的人拿起手机。
- 告警确认(Acknowledge)只是暂停升级,不是解决。 确认后 30 分钟内没有解决或更新状态,应该再次触发通知。
- 升级策略不应超过 4 级。 如果 4 级升级后仍然没有响应,说明流程有严重问题,需要修复的是流程而不是增加第 5 级。
6.3 人因工程视角
告警系统的设计不仅是技术问题,也是人因工程(Human Factors Engineering)问题。以下几个人因工程原则直接影响告警系统的设计。
信号检测理论(Signal Detection Theory):人类对信号(真正的告警)和噪声(误报)的区分能力取决于两个因素——信号强度和判断标准。当噪声太多时,人会提高判断标准(变得更保守),导致对真正的信号也变得迟钝。这就是为什么降低误报率比提高检出率更重要。
注意力资源理论(Attentional Resource Theory):人的注意力是有限的。每一条告警,无论真假,都消耗一定的认知资源。在高噪声环境中,工程师会发展出”告警忽略”的习惯——不再逐条阅读告警内容,而是快速滑过或批量确认。这种行为在心理学上称为”习惯化(Habituation)“。
信息超载(Information Overload):当信息量超过人的处理能力时,决策质量不是线性下降,而是断崖式下降。Miller 在 1956 年的经典论文中指出,人类短期记忆的容量约为 7 加减 2 个信息单元。当一个 On-Call 工程师同时面对 15 条未确认告警时,他的信息处理能力已经严重超载。
基于这些原则,告警系统的设计应该遵循以下准则:
- 每条告警必须是可操作的(Actionable)。 如果工程师看到一条告警后的正确反应是”什么都不做”,那这条告警就不应该存在。
- 每条告警必须指向明确的操作步骤。 告警的注释(Annotation)中应该包含 Runbook 链接,而不是仅仅告诉你”某个指标高了”。
- 同一时间段内发送给一个人的告警数量应尽可能少。 利用分组、抑制和优先级过滤来减少通知数量。
七、告警 Runbook 的设计
7.1 为什么 Runbook 至关重要
一条告警如果没有对应的 Runbook(运维操作手册),就像一个没有处方的诊断——告诉你病了,但不告诉你该吃什么药。
在凌晨三点,一个被叫醒的工程师的认知能力是白天的 60-70%。这时候让他从零开始排查一个不熟悉的问题,平均解决时间会比白天长 3-5 倍。但如果有一份清晰的 Runbook,他只需要按照步骤执行,解决时间可以和白天相当。
7.2 Runbook 的标准结构
一个好的 Runbook 应该包含以下部分:
# SLOBurnRateCritical Runbook
## 告警含义
此告警表示服务的 SLO 错误预算正在以 14.4 倍速率消耗。
如果当前错误率持续,预计约 50 小时内耗尽 30 天的全部错误预算。
## 影响范围
- 直接影响:用户可见的错误率上升
- 间接影响:下游服务可能因超时而受到级联影响
## 排查步骤
### 步骤 1:确认告警是否有效
1. 打开 Grafana 面板:[链接]
2. 查看 SLO 面板,确认错误率趋势
3. 如果错误率已经恢复,确认告警并记录为"自动恢复"
### 步骤 2:确定根因
1. 查看最近的部署记录:[链接]
2. 检查上游依赖的健康状态:[链接]
3. 检查基础设施指标(CPU、内存、磁盘 I/O):[链接]
### 步骤 3:常见根因及处理方式
| 根因 | 检查方式 | 处理方式 |
|------|---------|---------|
| 最近的代码部署引入 bug | 查看部署历史 | 回滚到上一个版本 |
| 上游数据库响应慢 | 查看数据库慢查询日志 | 联系 DBA On-Call |
| 节点资源耗尽 | 检查 CPU/内存使用率 | 触发自动扩缩容或手动扩容 |
| 外部依赖故障 | 检查第三方服务状态页 | 启用降级策略 |
### 步骤 4:升级条件
如果在 15 分钟内无法定位根因,升级到 Level 2。
如果影响范围扩大到其他服务,立即启动 Incident 流程。
## 联系人
- 服务 Owner:@payment-team
- DBA On-Call:@dba-oncall
- 平台 SRE:@sre-oncall7.3 Runbook 与告警规则的绑定
在 Prometheus 告警规则中,通过
annotations.runbook 字段将告警与 Runbook
绑定:
annotations:
runbook: "https://wiki.internal/runbooks/slo-burn-rate-critical"Runbook 应该和代码一样版本管理——放在 Git 仓库中,通过 PR 流程维护,每次告警规则变更时同步更新对应的 Runbook。
八、工程案例:从每周 200 条告警到每周 15 条
8.1 背景
某中型互联网公司的订单服务团队(6 名后端工程师,2 名 SRE)在 2023 年初面临严重的告警疲劳问题。团队维护着一个由 12 个微服务组成的订单处理系统,On-Call 轮换周期为一周。
当时的告警现状:
- 告警规则总数:187 条
- 每周平均告警数:约 210 条
- 可操作告警比例:约 12%(即约 25 条实际需要操作)
- On-Call 平均确认时间:18 分钟
- 凌晨被叫醒次数:每周人均 4-5 次
- 过去 6 个月有 2 名 SRE 因 On-Call 压力离职
8.2 诊断过程
团队用三周时间对所有告警进行了分类统计:
| 告警类别 | 数量占比 | 描述 |
|---|---|---|
| 无需操作的毛刺 | 38% | 短暂的延迟或错误率毛刺,自动恢复 |
| 已知但未修复的问题 | 22% | 某个服务的已知 bug,每次告警都被手动确认 |
| 重复告警 | 15% | 同一根因触发的多条告警 |
| 阈值过低 | 13% | 阈值设置不合理,正常波动就触发 |
| 真正需要操作的告警 | 12% | 需要工程师介入处理的问题 |
8.3 改造方案
团队实施了以下五个步骤的改造:
步骤一:删除所有不可操作的告警。 对 187 条规则逐条审查,问一个问题:“收到这条告警后,工程师应该做什么?”如果答案是”什么都不做”或”等它自己恢复”,直接删除。这一步删掉了 91 条规则。
步骤二:合并重复告警。 多个规则监控同一个问题的不同维度(比如 CPU 高、内存高、延迟高,但根因都是同一个服务过载),合并为一条综合性告警。这一步又减少了 34 条规则。
步骤三:引入基于 SLO 的燃烧率告警。 为 12 个微服务中最核心的 5 个定义了 SLO,用多窗口燃烧率规则替换原有的阈值告警。每个服务的告警规则从平均 15 条减少到 3 条(P1/P2/P3 各一条)。
步骤四:配置 Alertmanager 的分组和抑制。
按服务和告警名称分组,添加抑制规则(基础设施层告警抑制应用层告警),配置合理的
group_wait 和 group_interval。
步骤五:为每条保留的告警编写 Runbook。 最终保留了 42 条告警规则,团队花了两周时间为每条规则编写了对应的 Runbook。
8.4 改造结果
改造前后的对比:
| 指标 | 改造前 | 改造后 | 变化 |
|---|---|---|---|
| 告警规则数 | 187 条 | 42 条 | -77% |
| 每周告警数 | 约 210 条 | 约 18 条 | -91% |
| 可操作比例 | 12% | 83% | +71 个百分点 |
| 平均确认时间 | 18 分钟 | 3 分钟 | -83% |
| 每周凌晨被叫醒 | 4-5 次 | 0-1 次 | -80% 以上 |
| MTTR(平均恢复时间) | 47 分钟 | 22 分钟 | -53% |
每周告警数从 210 条降到 18 条——降幅超过 91%。但更重要的是,MTTR 从 47 分钟降到 22 分钟。告警数量减少了,但真正的故障被发现和解决得更快了。这看似矛盾,实际上完全符合逻辑:当每条告警都是可操作的,工程师的注意力集中在真正重要的信号上,响应速度自然提升。
8.5 关键教训
这次改造给出了几个重要教训:
- 先减后加。 不要在现有的告警规则上叠加新规则。先做减法——审计并删除所有不可操作的告警——然后再重新设计。
- SLO 需要业务方参与。 SLO 的目标值不是技术团队自己定的,需要和产品经理、业务负责人一起协商。99.9% 和 99.99% 的差异对应着完全不同的工程投入。
- Runbook 不是可选项,是必需品。 没有 Runbook 的告警是半成品。改造过程中发现,很多告警之所以不可操作,是因为当初写告警的人都不知道收到告警后应该做什么。
- 持续审计。 告警质量不是一次性工程。团队现在每月进行一次告警审计会议(Alert Review Meeting),审查过去一个月的所有告警,讨论哪些告警是有价值的,哪些需要调整。
九、阈值告警 vs SLO 告警:全面对比
9.1 核心差异对比
| 维度 | 阈值告警(Threshold-Based) | SLO 告警(SLO-Based) |
|---|---|---|
| 告警触发条件 | 单一指标超过固定阈值 | 错误预算消耗速率超过阈值 |
| 与用户体验的关联 | 间接(需要人工映射) | 直接(SLO 本身就定义了用户体验的边界) |
| 误报率 | 高(无法适应流量模式变化) | 低(多窗口机制过滤毛刺) |
| 漏报率 | 中(缓慢退化容易错过) | 低(长窗口捕获缓慢退化) |
| 规则数量 | 多(每个指标一条或多条) | 少(每个 SLO 3 条,分 P1/P2/P3) |
| 维护成本 | 高(阈值需要频繁调整) | 低(SLO 目标相对稳定) |
| 严重程度判断 | 人工标注(P1/P2/P3) | 自动推导(由燃烧率决定) |
| 适用场景 | 简单系统、已知边界值的硬指标 | 面向用户的服务、需要量化可靠性的系统 |
| 前置条件 | 无 | 需要定义 SLO,需要团队对 SLO 有共识 |
| 典型代表 | Nagios、Zabbix 时代的实践 | Google SRE、现代可观测性平台 |
9.2 混合策略
SLO 告警并不能完全替代阈值告警。以下场景仍然适合使用阈值告警:
- 硬件指标的绝对边界:磁盘使用率超过 95% 需要立即扩容,这和 SLO 无关。
- 安全相关指标:异常登录次数超过阈值、证书到期时间少于 7 天,这些是绝对的边界条件。
- 成本控制:云资源费用超过预算的 80%,需要及时通知。
- 基础设施健康:节点宕机、网络分区,这些是基础设施层面的硬故障。
推荐的混合策略是:
- 应用层:用 SLO 燃烧率告警覆盖可用性和延迟
- 基础设施层:用阈值告警覆盖资源饱和度和硬故障
- 业务层:用阈值告警覆盖业务指标的异常波动(如订单量突然归零)
- 安全层:用阈值告警覆盖安全事件
十、On-Call 实践与告警的关系
10.1 On-Call 轮换设计
告警策略不能脱离 On-Call 制度来讨论。最好的告警规则配上最差的 On-Call 制度,效果还是差。
On-Call 轮换设计的核心原则:
- 轮换周期:3-5 天一轮,不要超过 7 天。PagerDuty 的数据显示,连续 On-Call 超过 5 天后,工程师的响应质量开始显著下降。
- 主备双人制:每个时段至少有一个主 On-Call 和一个副 On-Call。主 On-Call 未响应时自动升级到副 On-Call。
- 工作量补偿:On-Call 期间如果凌晨被叫醒,第二天应该有调休补偿。这不仅是人文关怀,也是工程效率的需要——一个睡眠不足的工程师写出 bug 的概率更高。
- On-Call 不等于值班:On-Call 工程师的职责是响应告警,不是坐在电脑前盯着监控面板。如果需要有人持续盯着面板,说明告警系统不够好。
10.2 告警回顾会议
每周或每两周举行一次告警回顾会议(Alert Review),是维持告警质量的关键机制。
会议议程:
- 回顾过去一周/两周的所有告警。
逐条过,对每条告警问三个问题:
- 这条告警是否可操作?
- 响应方式是否正确?
- 是否有改进空间?
- 统计误报率。 计算可操作告警的比例。如果低于 70%,需要开始削减告警。
- 识别缺失的告警。 回顾过去一周是否有故障没被告警捕获。
- 更新 Runbook。 根据实际排查经验更新 Runbook 中的步骤。
10.3 Incident 与告警的衔接
当告警升级为 Incident(事件)时,需要一套清晰的流程:
告警触发 -> On-Call 确认 -> 初步排查 -> 判断是否需要升级为 Incident
|
v
启动 Incident 流程
1. 创建 Incident 频道
2. 指定 Incident Commander
3. 拉入相关工程师
4. 持续沟通状态更新
5. 故障恢复后写 Post-Mortem
告警到 Incident 的升级条件通常包括:
- 影响范围超过单个服务
- 预计恢复时间超过 30 分钟
- 用户可感知的服务降级
- 数据完整性受到威胁
十一、高级告警模式
11.1 基于延迟分布的 SLO 告警
前面的例子都是基于错误率的 SLO。延迟 SLO 的实现略有不同,因为延迟是一个分布而不是一个比率。
延迟 SLO 的典型定义是:
99% 的请求应在 300ms 内完成。
对应的 Prometheus 实现:
groups:
- name: latency-slo
rules:
# 计算满足延迟 SLO 的请求比例(延迟 < 300ms 的请求占比)
- record: slo:latency_good_ratio:ratio_rate5m
expr: |
(
sum(rate(http_request_duration_seconds_bucket{le="0.3"}[5m]))
/
sum(rate(http_request_duration_seconds_count[5m]))
)
- record: slo:latency_good_ratio:ratio_rate1h
expr: |
(
sum(rate(http_request_duration_seconds_bucket{le="0.3"}[1h]))
/
sum(rate(http_request_duration_seconds_count[1h]))
)
- name: latency-burn-rate-alerts
rules:
- alert: LatencySLOBurnRateCritical
expr: |
(
(1 - slo:latency_good_ratio:ratio_rate1h) > (14.4 * 0.01)
and
(1 - slo:latency_good_ratio:ratio_rate5m) > (14.4 * 0.01)
)
for: 2m
labels:
severity: critical
slo: "latency-p99-300ms"
annotations:
summary: "延迟 SLO 错误预算正在以 14.4 倍速率消耗"
runbook: "https://wiki.internal/runbooks/latency-slo-critical"注意这里的 SLO 目标是 99%(而不是 99.9%),所以错误预算是 1%(0.01),而不是 0.1%(0.001)。延迟 SLO 的目标值通常比可用性 SLO 低一个数量级,因为延迟波动的自然方差更大。
11.2 复合 SLO
实际生产中,一个服务的 SLO 通常是多维度的:
SLO 1(可用性):99.9% 的请求成功返回
SLO 2(延迟):99% 的请求在 300ms 内完成
SLO 3(吞吐量):系统能处理至少 1000 QPS
每个 SLO 独立计算燃烧率,独立告警。但在展示层面,Grafana 面板通常会展示一个综合的 SLO 健康度视图:
综合 SLO 健康度 = min(SLO1 预算剩余比例, SLO2 预算剩余比例, ...)
取最差的那个维度作为综合健康度——木桶原理。
11.3 SLO 的动态调整
SLO 不是一成不变的。当团队的工程能力提升时,SLO 可以收紧;当业务进入快速迭代期(需要更多变更、接受更多风险)时,SLO 可以适当放宽。
调整 SLO 的决策框架:
IF 过去 3 个月错误预算剩余 > 50%:
-> 可以考虑收紧 SLO(当前目标太容易达到,可能限制了迭代速度)
-> 或者利用剩余预算加速功能开发
IF 过去 3 个月错误预算频繁耗尽:
-> 需要投入更多工程资源提升可靠性
-> 或者放宽 SLO(当前目标超出了团队的工程能力)
IF 用户投诉率上升但 SLO 仍在达标:
-> SLI 的选择可能有问题(指标没有反映用户体验)
-> 需要重新审视 SLI 的定义
十二、告警系统的可观测性
12.1 监控你的监控
告警系统本身也需要被监控——这不是递归,而是必要的保障。以下是告警系统应该自监控的指标:
# 告警系统的自监控规则
groups:
- name: alerting-meta-monitoring
rules:
# Alertmanager 的通知发送失败率
- alert: AlertmanagerNotificationsFailing
expr: |
rate(alertmanager_notifications_failed_total[5m])
/
rate(alertmanager_notifications_total[5m])
> 0.01
for: 5m
labels:
severity: critical
annotations:
summary: "Alertmanager 的通知发送失败率超过 1%"
# Prometheus 规则评估耗时过长
- alert: PrometheusRuleEvaluationSlow
expr: |
prometheus_rule_group_last_duration_seconds
> prometheus_rule_group_interval_seconds
for: 10m
labels:
severity: warning
annotations:
summary: "Prometheus 规则评估耗时超过评估间隔"
# 确保 Alertmanager 集群成员健康
- alert: AlertmanagerClusterMemberDown
expr: |
count(alertmanager_cluster_members) < 3
for: 5m
labels:
severity: critical
annotations:
summary: "Alertmanager 集群成员少于预期数量"12.2 告警质量指标
除了技术指标外,还应该定期跟踪告警质量的业务指标:
- 可操作率(Actionable Rate):过去 30 天内,导致工程师实际采取操作的告警占总告警的比例。目标值:大于 80%。
- 检出率(Detection Rate):过去 30 天内,被告警捕获的故障占总故障的比例。目标值:大于 95%。
- 平均检出时间(MTTD, Mean Time to Detect):从故障实际发生到告警触发的平均时间。目标值:小于 5 分钟。
- 告警 MTTR:从告警触发到问题解决的平均时间。
- 误报率(False Positive Rate):不需要任何操作的告警占总告警的比例。目标值:小于 20%。
这些指标应该在每月的告警回顾会议上审查,作为持续改进的依据。
十三、工具链集成
13.1 完整的告警工具链
一个成熟的告警系统不是单一工具,而是一条工具链:
| 层次 | 工具 | 职责 |
|---|---|---|
| 指标采集 | Prometheus / VictoriaMetrics / Thanos | 采集和存储时序指标 |
| 规则评估 | Prometheus / Cortex Ruler | 评估告警规则 |
| 告警路由 | Alertmanager | 去重、分组、路由、抑制、静默 |
| 事件管理 | PagerDuty / OpsGenie / Grafana OnCall | 升级策略、On-Call 轮换、事件追踪 |
| 通知渠道 | Slack / 企业微信 / 钉钉 / 短信 / 电话 | 最终通知到人 |
| 可视化 | Grafana | SLO 面板、错误预算面板、告警历史 |
| 文档 | Wiki / Git 仓库 | Runbook、SLO 定义、告警规则文档 |
13.2 Grafana SLO 面板设计
一个好的 SLO 面板应该一目了然地展示以下信息:
- 当前错误预算剩余比例(百分比 + 时间)
- 当前燃烧率(实时 + 趋势)
- SLO 达标状态(达标 / 风险 / 违规)
- 过去 30 天的 SLI 趋势图
- 活跃告警列表
Grafana 面板的 PromQL 查询示例:
# 错误预算剩余比例(30 天窗口,SLO = 99.9%)
1 - (
sum(increase(http_requests_total{status=~"5.."}[30d]))
/
sum(increase(http_requests_total[30d]))
- 0.001
) / 0.001
# 当前燃烧率(1 小时窗口)
(
sum(rate(http_requests_total{status=~"5.."}[1h]))
/
sum(rate(http_requests_total[1h]))
) / 0.001
13.3 告警即代码(Alerts as Code)
告警规则应该像应用代码一样管理——版本控制、代码审查、自动化部署:
alerting-rules/
├── recording-rules/
│ ├── order-service.yaml
│ ├── payment-service.yaml
│ └── user-service.yaml
├── alerting-rules/
│ ├── slo-burn-rate/
│ │ ├── order-service.yaml
│ │ ├── payment-service.yaml
│ │ └── user-service.yaml
│ └── infrastructure/
│ ├── node-alerts.yaml
│ └── kubernetes-alerts.yaml
├── alertmanager/
│ ├── alertmanager.yaml
│ └── templates/
│ └── slack.tmpl
├── runbooks/
│ ├── slo-burn-rate-critical.md
│ ├── slo-burn-rate-high.md
│ └── node-disk-full.md
└── tests/
├── order-service-alerts_test.yaml
└── payment-service-alerts_test.yaml
告警规则的变更应该触发 CI/CD 流水线:
# .github/workflows/alerting-rules-ci.yaml
name: Alerting Rules CI
on:
pull_request:
paths:
- "alerting-rules/**"
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install promtool
run: |
wget -q https://github.com/prometheus/prometheus/releases/download/v2.51.0/prometheus-2.51.0.linux-amd64.tar.gz
tar xzf prometheus-*.tar.gz
sudo mv prometheus-*/promtool /usr/local/bin/
- name: Validate recording rules
run: promtool check rules alerting-rules/recording-rules/*.yaml
- name: Validate alerting rules
run: promtool check rules alerting-rules/alerting-rules/**/*.yaml
- name: Run unit tests
run: promtool test rules alerting-rules/tests/*.yaml
- name: Validate Alertmanager config
run: |
wget -q https://github.com/prometheus/alertmanager/releases/download/v0.27.0/alertmanager-0.27.0.linux-amd64.tar.gz
tar xzf alertmanager-*.tar.gz
./alertmanager-*/amtool check-config alerting-rules/alertmanager/alertmanager.yaml13.4 告警规则的单元测试
Prometheus 原生支持告警规则的单元测试,这是保证告警质量的重要手段:
# tests/order-service-alerts_test.yaml
rule_files:
- ../recording-rules/order-service.yaml
- ../alerting-rules/slo-burn-rate/order-service.yaml
evaluation_interval: 1m
tests:
# 测试场景 1:高错误率应触发 P1 告警
- interval: 1m
input_series:
- series: 'http_requests_total{service="order", status="200"}'
values: "0+980x120" # 每分钟 980 个成功请求
- series: 'http_requests_total{service="order", status="500"}'
values: "0+20x120" # 每分钟 20 个失败请求(错误率 2%)
alert_rule_test:
- eval_time: 65m # 1 小时 + 5 分钟后评估
alertname: SLOBurnRateCritical
exp_alerts:
- exp_labels:
severity: critical
slo: "availability-99.9"
exp_annotations:
summary: "SLO 错误预算正在以 14.4 倍速率消耗"
# 测试场景 2:短暂毛刺不应触发告警
- interval: 1m
input_series:
- series: 'http_requests_total{service="order", status="200"}'
values: "0+990x60 60000+1000x60" # 前 60 分钟正常,后恢复
- series: 'http_requests_total{service="order", status="500"}'
values: "0+10x55 550+200x5 1550+0x60" # 55-60 分钟有毛刺
alert_rule_test:
- eval_time: 70m
alertname: SLOBurnRateCritical
exp_alerts: [] # 不应触发告警十四、常见反模式
告警系统的设计中有几个反复出现的反模式,值得专门列出来作为检查清单。
14.1 反模式一:“宁可多报不漏报”
这是最常见的反模式。它的逻辑是:“万一这是一个真正的故障呢?告警多一点总比漏掉好。”
问题在于:告警不是日志。日志可以多写一些,反正可以事后检索。但告警是直接推送给人的通知,每一条都消耗工程师的注意力。当告警太多时,工程师不会逐条处理,而是批量忽略——这时候不仅多报的没用,真正重要的也被一起忽略了。
正确的做法是:宁可少报不多报。先确保每条告警都是可操作的,然后通过事后审计(Post-Mortem)发现漏报的情况,针对性地补充告警。
14.2 反模式二:告警规则只增不减
很多团队有一个不成文的规矩:出过一次事故就加一条告警。加了之后永远不删。三年下来,告警规则从 20 条膨胀到 500 条,其中大量规则早已过时——对应的服务已经下线、架构已经重构、问题已经从根本上修复了。
正确的做法是:每条告警规则都应该有一个 Owner 和一个过期日期。定期审计,删除不再需要的规则。
14.3 反模式三:用告警替代仪表盘
有些团队会设置大量”信息性”告警——“QPS 超过 5000”、“缓存命中率低于 90%”——这些不是问题,只是观察。它们应该出现在 Grafana 面板上,而不是出现在 PagerDuty 里。
判断标准很简单:收到这条告警后,你需要在接下来 30 分钟内做什么? 如果答案是”看看面板”或”继续观察”,它就不是告警,而是仪表盘上的一个面板。
14.4 反模式四:缺少上下文的告警
[ALERT] HighCPU - CPU usage is high on node-17
这条告警缺少太多信息:CPU 是多高?持续了多久?这个节点上跑着什么服务?应该做什么?
好的告警通知应该包含:
- 当前值(比如 CPU 使用率 94%)
- 阈值或触发条件
- 持续时间
- 影响范围
- Grafana 面板链接
- Runbook 链接
- 相关服务和 Owner
14.5 反模式五:所有告警都是 P1
当每条告警都标记为 critical 时,没有一条是 critical。严重程度的分级必须被严格执行:
- P1(Critical):用户正在受到影响,需要立即响应。可以在凌晨叫醒人。
- P2(Warning):问题存在,但影响有限。需要在 30 分钟内响应,但不需要叫醒人。
- P3(Info):需要关注的趋势。在工作时间处理即可。
一个健康的告警系统中,P1 告警应该占总告警的 10-15%,P2 占 30-40%,P3 占 50-60%。如果 P1 占了超过 30%,说明严重程度的标准太松了。
十五、从告警到可靠性文化
15.1 告警是可靠性工程的最后一道防线
告警系统在可靠性工程中的位置,可以用下面这个层次模型来理解:
第 1 层:预防(Prevention)
- 代码审查、自动化测试、混沌工程
- 目标:减少故障发生
第 2 层:检测(Detection)
- 监控系统、日志分析、分布式追踪
- 目标:快速发现故障
第 3 层:通知(Notification)
- 告警系统
- 目标:把故障信息传递给正确的人
第 4 层:响应(Response)
- On-Call 制度、Runbook、Incident 管理
- 目标:快速恢复服务
第 5 层:学习(Learning)
- Post-Mortem、告警回顾、SLO 审计
- 目标:从故障中学习,防止再次发生
告警系统处于第 3 层——通知。它的上游是监控系统(检测),下游是 On-Call 制度(响应)。一个好的告警系统不能弥补差的监控或差的 On-Call 制度,但一个差的告警系统可以毁掉好的监控和好的 On-Call 制度。
15.2 SLO 驱动的工程决策
SLO 不仅仅是告警的触发条件,它还是工程团队做决策的框架。
错误预算充足时:团队可以大胆地发布新功能、做实验、承担更多技术风险。偶尔出点小问题,错误预算可以吸收。
错误预算紧张时:冻结非关键变更,集中精力修复可靠性问题。只有修复可靠性 bug 和安全漏洞的变更才允许发布。
错误预算耗尽时:全面冻结变更(Change Freeze),直到预算恢复。这个决策不需要管理层审批——SLO 政策提前定义好,耗尽就冻结,这是自动化的流程约束。
这种机制的好处是:它把”可靠性 vs 功能开发”的争论变成了一个数学问题。不需要产品经理和 SRE 吵架——数据说了算。
15.3 持续改进闭环
告警系统的质量改进是一个持续的闭环:
定义 SLO -> 实现告警规则 -> 运行告警 -> 收集反馈
^ |
| v
+--- 调整 SLO 和告警规则 <--- 告警回顾会议
每次告警回顾会议产出的行动项通常包括:
- 删除或合并冗余告警规则
- 调整燃烧率阈值或窗口大小
- 更新 Runbook
- 补充漏报场景的告警
- 调整 SLO 目标值(需要和业务方协商)
- 优化 Alertmanager 的分组和路由配置
参考资料
- Beyer, B., Jones, C., Petoff, J., Murphy, N.R.(2016). Site Reliability Engineering: How Google Runs Production Systems. O’Reilly Media.
- Beyer, B., Murphy, N.R., Rensin, D.K., Kawahara, K., Thorne, S.(2018). The Site Reliability Workbook: Practical Ways to Implement SRE. O’Reilly Media. 第 5 章 “Alerting on SLOs” 是多窗口燃烧率算法的原始出处。
- PagerDuty(2020). State of Digital Operations. https://www.pagerduty.com/resources/reports/digital-operations/
- Google Cloud. SRE implements DevOps - Alerting on SLOs. https://sre.google/workbook/alerting-on-slos/
- Prometheus Documentation. Alerting Rules. https://prometheus.io/docs/prometheus/latest/configuration/alerting_rules/
- Prometheus Documentation. Alertmanager Configuration. https://prometheus.io/docs/alerting/latest/configuration/
- Sloss, B., Tong, C.(2017). “Alerting on SLOs”. Google SRE Con.
- PagerDuty. Incident Response Guide. https://response.pagerduty.com/
- Miller, G.A.(1956). “The magical number seven, plus or minus two: some limits on our capacity for processing information”. Psychological Review, 63(2), 81-97.
- Wilkinson, J.(2019). “Implementing SLOs in Practice”. SREcon19 Americas.
- Hidalgo, A.(2020). Implementing Service Level Objectives. O’Reilly Media.
- Majors, C., Fong-Jones, L., Miranda, G.(2022). Observability Engineering. O’Reilly Media. 第 16-18 章关于 SLO 和告警的讨论。
导航
上一篇:分布式追踪
下一篇:部署架构
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【系统架构设计百科】SLO 工程:可靠性的量化管理
SLI、SLO、SLA 不只是运维指标——它们是架构决策的定量依据。本文从 Google SRE 的 Error Budget 策略出发,拆解多窗口燃烧率告警的数学原理,讲清楚 SLO 如何在产品与工程的冲突中充当仲裁者,并给出基于 Prometheus 和 Grafana 的落地方案。
【系统架构设计百科】架构质量属性:不只是"高可用高性能"
需求评审时写下的'高可用、高性能、高并发',到了架构设计阶段几乎无法落地——因为它们不是可执行的需求。本文从 SEI/CMU 的质量属性理论出发,用 stimulus-response 场景模型把模糊需求变成可量化、可验证的架构约束,并拆解属性之间的冲突与联动关系。
【系统架构设计百科】复杂性管理:架构的核心战场
系统复杂性是架构腐化的根源——本文从 Brooks 的本质复杂性与偶然复杂性划分出发,结合认知负荷理论与 Parnas 的信息隐藏原则,系统阐述复杂性的来源、度量与控制手段,并给出可操作的架构策略
【系统架构设计百科】微服务架构深度审视:优势、代价与适用边界
微服务不是免费的午餐。本文从分布式系统八大谬误出发,拆解微服务真正解决的问题与引入的代价,梳理服务边界划分的工程方法论,还原 Amazon 和 Netflix 从单体到微服务的真实演进时间线,给出微服务适用与不适用的判断框架。