概述
SRE 有一句名言:“系统一定会出故障,区别在于你是被它叫醒的还是主动管理它的。” 事件管理不是"出了事再处理",而是一套从预防、检测、响应到学习的完整工程体系。
从事件分级、On-Call 轮值、事故响应流程、Postmortem 文化、告警治理五个方面,详细梳理如何构建一个可落地的 On-Call 体系。
关于事件管理的系统方法论,可参考 Google SRE Book - Managing Incidents 和 Google SRE Book - Postmortem Culture。
一、事件分级标准
没有分级的事件管理等于没有管理——所有事件都按紧急处理,结果就是没有真正的紧急。合理的事件分级是 On-Call 体系的基石。
P0-P4 事件定义
| 级别 | 定义 | 影响范围 | 响应时效 | 示例 |
|---|---|---|---|---|
| P0 | 生产服务完全不可用 | 全量用户受影响 | 立即响应,<5min | 核心服务宕机、数据库不可用 |
| P1 | 核心功能严重降级 | 大量用户受影响 | <15min | 支付失败率飙升、API 错误率 >10% |
| P2 | 部分功能降级 | 部分用户受影响 | <30min | 某区域延迟劣化、非核心服务异常 |
| P3 | 潜在风险 | 暂无直接影响 | <2h(工作时间) | 磁盘水位 >80%、单节点故障 |
| P4 | 优化建议 | 无影响 | 下一工作日 | 告警阈值优化、文档补充 |
分级原则
分级的本质是资源调度优先级——让有限的人力优先处理影响最大的问题:
- 以用户影响为准,而非技术指标:CPU 99% 是 P3,但如果导致用户请求超时就是 P1
- 明确定义,避免模糊:“大量用户"是 30% 还是 50%?需要量化
- 可自动判定:理想状态下,事件级别应该能通过告警规则自动确定
# 告警分级规则示例
groups:
- name: incident-grading
rules:
# P0: 核心服务完全不可用
- alert: P0ServiceDown
expr: up{job="critical-service"} == 0
for: 1m
labels:
severity: P0
page: true # 触发电话告警
escalation: true # 自动升级到主管
annotations:
summary: "核心服务不可用"
runbook: "https://wiki/incident/p0-service-down"
# P1: 错误率超过 SLO
- alert: P1HighErrorRate
expr: |
sum(rate(http_requests_total{status=~"5.."}[5m]))
/
sum(rate(http_requests_total[5m])) > 0.05
for: 2m
labels:
severity: P1
page: true
annotations:
summary: "错误率超过 5%"
runbook: "https://wiki/incident/high-error-rate"
# P2: 延迟劣化
- alert: P2LatencyDegraded
expr: |
histogram_quantile(0.99,
sum(rate(http_request_duration_seconds_bucket[5m])) by (le)
) > 0.5
for: 5m
labels:
severity: P2
page: false # 仅通知,不电话呼叫
slack: "#alerts-prod"
二、On-Call 轮值机制设计
基本原则
On-Call 机制的核心目标是:让正确的人在正确的时间收到正确的告警。设计时需遵循以下原则:
- 可持续:On-Call 不是惩罚,不能让个别人长期超负荷
- 主备制:始终有 Primary + Secondary 两层保障
- 明确边界:响应时间、处理范围、升级路径必须清晰
- 尊重生活:夜间告警必须有补偿,避免 burnout
轮值排班设计
主备轮值制
# On-Call 排班配置示例
oncall_schedule:
timezone: "Asia/Shanghai"
# Primary:第一响应人
primary:
rotation: weekly # 每周轮换
members:
- name: "张三"
phone: "+86-138xxxx0001"
- name: "李四"
phone: "+86-138xxxx0002"
- name: "王五"
phone: "+86-138xxxx0003"
handoff_time: "10:00" # 每周一 10:00 交接
response_sla:
P0: "5min"
P1: "15min"
P2: "30min"
# Secondary:升级兜底人
secondary:
rotation: weekly
members:
- name: "赵六"
phone: "+86-138xxxx0004"
- name: "孙七"
phone: "+86-138xxxx0005"
escalation_after: "10min" # Primary 10min 未响应,升级到 Secondary
# Manager Escalation
manager:
escalation_after: "30min" # Secondary 30min 未响应,升级到 Manager
members:
- name: "周经理"
phone: "+86-138xxxx0006"
# 假期覆盖
holidays:
- date: "2026-01-01"
primary: "张三"
secondary: "李四"
Follow-the-Sun 模式
对于全球化团队,可以采用 Follow-the-Sun 模式,让 On-Call 总是在白天进行:
时区 08:00 - 20:00 覆盖
──────────────────────────────────
北京 (UTC+8) | 轮值 A |
伦敦 (UTC+0) | 轮值 B |
旧金山 (UTC-8) | 轮值 C |
──────────────────────────────────
全天覆盖,无夜间告警
排班表示例
Week of 2026-07-06 ~ 2026-07-12
┌──────────┬──────────┬──────────┐
│ 日期 │ Primary │ Secondary│
├──────────┼──────────┼──────────┤
│ 周一 7/6 │ 张三 │ 赵六 │
│ 周二 7/7 │ 张三 │ 赵六 │
│ 周三 7/8 │ 张三 │ 赵六 │
│ 周四 7/9 │ 李四 │ 赵六 │
│ 周五 7/10│ 李四 │ 孙七 │
│ 周六 7/11│ 李四 │ 孙七 │
│ 周日 7/12│ 王五 │ 孙七 │
└──────────┴──────────┴──────────┘
交接时间:每日 10:00 (Asia/Shanghai)
响应工具:PagerDuty / OpsGenie / 自研告警平台
On-Call 健康度指标
On-Call 机制的可持续性需要量化监控:
| 指标 | 健康范围 | 预警阈值 |
|---|---|---|
| 每周告警次数 | <10 次 | >20 次 |
| 夜间告警次数 | <2 次/周 | >5 次/周 |
| 平均响应时间 | <5min (P0) | >15min |
| 平均处理时间 | <1h | >4h |
| On-Call 工时占比 | <25% | >50% |
如果告警次数超过预警阈值,说明系统存在系统性问题,需要立即治理而非加人。
三、事故响应流程
Google 提出了结构化的事故响应模型——SEV 模型,将事故响应分为五个阶段:
Detection → Triage → Mitigation → Resolution → Postmortem
检测 分级 缓解 解决 复盘
1. Detection(检测)
检测是事故响应的第一步,也是最重要的环节。检测手段包括:
- 告警系统:基于 SLO 的主动告警(而非被动阈值)
- 用户反馈:客服工单、社区舆情
- 主动巡检:定期健康检查、混沌工程
检测的黄金指标:
| 指标 | 目标 |
|---|---|
| MTTD(平均检测时间) | <1min(自动检测) / <15min(人工发现) |
| 误报率 | <5% |
| 检出率 | >95% |
2. Triage(分级)
确认告警后,第一件事不是修故障,而是分级:
- 确认影响范围:哪些用户受影响?哪些功能受影响?
- 确定事件级别:根据 P0-P4 分级标准定级
- 决定响应模式:单人处理 / 组建虚拟团队 / 升级
3. Mitigation(缓解)
缓解的核心原则是:先止血,后治病。
- 不是找根因:根因分析是 Postmortem 阶段的事
- 是恢复服务:回滚、切流、扩容、降级,任何能让用户恢复的手段
常见的缓解手段优先级:
1. 回滚(最快) → 部署有问题
2. 切流(次快) → 某区域/某节点有问题
3. 扩容(较慢) → 容量不足
4. 降级(兜底) → 关闭非核心功能保核心
5. 重启(最后) → 临时恢复,根因待查
4. Resolution(解决)
缓解之后是根本解决——修复根因、恢复全量服务、验证系统状态:
# 事故解决检查清单
- [ ] 根因已确认并修复
- [ ] 受影响服务已恢复到 SLO 范围
- [ ] 所有节点状态正常
- [ ] 监控告警已恢复正常
- [ ] 受影响用户已确认恢复
- [ ] 事后改进项已记录
5. Postmortem(复盘)
详见下一节。
四、Postmortem 文化
Blameless 原则
Postmortem 的第一条原则是 Blameless——无指责。
“The purpose of a postmortem is to learn from the incident, not to assign blame.” 来源:Google SRE Book - Postmortem Culture
无指责不是不追究责任,而是追究系统责任而非个人责任。假设同样的错误换了任何人都会犯,那问题就出在系统设计上,而不是人身上。
Postmortem 模板
以下是一个生产级的 Postmortem 模板:
# Postmortem: [事件标题]
## 基本信息
| 字段 | 内容 |
|------|------|
| 事件级别 | P0 |
| 发生时间 | 2026-07-08 14:32 UTC+8 |
| 恢复时间 | 2026-07-08 15:48 UTC+8 |
| 持续时长 | 1h16min |
| 影响范围 | 全站用户无法下单,约 12 万用户受影响 |
| 撰写人 | 张三 |
| 审核人 | 周经理 |
| 状态 | 已完成 |
## 事件摘要
7 月 8 日 14:32,订单服务因数据库连接池耗尽导致全站无法下单。
故障持续 76 分钟,影响约 12 万用户,预估损失订单金额约 ¥230 万。
根因为一次配置变更未经过灰度发布直接全量推送,导致连接池参数错误。
## 时间线
| 时间 | 事件 |
|------|------|
| 14:32 | 告警系统触发 P0 告警:订单服务错误率 100% |
| 14:35 | On-Call 工程师张三确认告警,开始排查 |
| 14:38 | 确认为数据库连接池耗尽,大量请求超时 |
| 14:42 | 发现 14:25 有一笔配置变更(连接池上限从 200 改为 50)|
| 14:45 | 执行回滚,恢复连接池配置 |
| 14:52 | 订单服务开始恢复,错误率下降 |
| 15:10 | 错误率恢复至 SLO 范围 |
| 15:48 | 全量验证完成,事件关闭 |
## 根因分析
### 直接原因
配置变更将数据库连接池上限从 200 误改为 50,导致高并发下连接池耗尽。
### 深层原因
1. 变更未经灰度发布,直接全量推送
2. 配置变更缺少自动化校验(连接池下限不应低于 100)
3. 变更审批流程未覆盖配置类变更
4. 缺少配置变更的回滚预案
## 影响评估
- 受影响用户:约 12 万
- 业务影响:损失订单约 ¥230 万
- 声誉影响:社交媒体投诉 47 条
- SLI 违约:订单可用性 SLO (99.9%) 本月错误预算消耗 100%
## 改进项(Action Items)
| 编号 | 改进项 | 负责人 | 截止日期 | 优先级 |
|------|--------|--------|---------|--------|
| #1 | 配置变更强制灰度发布 | 李四 | 2026-07-15 | P0 |
| #2 | 连接池参数自动化校验(下限不低于 100) | 王五 | 2026-07-20 | P0 |
| #3 | 配置变更纳入审批流程 | 赵六 | 2026-07-25 | P1 |
| #4 | 订单服务增加连接池水位告警 | 张三 | 2026-07-12 | P1 |
| #5 | 制定配置变更回滚 SOP | 孙七 | 2026-07-18 | P2 |
## 经验教训
### 做得好的
- 告警检测及时(MTTD < 3min)
- On-Call 响应迅速(确认 + 排查 < 10min)
- 回滚决策果断,没有过度分析根因
### 做得不好的
- 配置变更缺少灰度机制
- 连接池参数缺少合理下限保护
- 配置类变更游离于审批流程之外
## 附注
- 相关告警记录: alert-20260708-1432
- 相关变更记录: change-20260708-1425
- 相关监控图表: grafana dashboard "订单服务概览"
Postmortem 的执行要点
- 及时性:事件解决后 48 小时内 完成复盘,趁记忆清晰
- 参与度:所有相关人员参与,包括开发、运维、产品
- 可追踪:每个 Action Item 必须有负责人和截止日期
- 可共享:Postmortem 文档全员可见,组织级知识沉淀
- 闭环:定期回顾 Action Item 完成情况,确保落地
五、告警疲劳治理
告警疲劳的危害
告警疲劳(Alert Fatigue)是 On-Call 的头号杀手。当告警数量超过人的处理能力时,On-Call 工程师会开始忽略告警——这正是最危险的时刻。很多重大事故的根因,都包含"告警被忽略"这个环节。
识别告警疲劳
# 每周告警总量趋势
sum by (week) (rate(ALERTS_FIRED[7d]))
# 告警噪声比(误报率 = 已触发但未确认处理的告警比例)
sum(ALERTS_FIRED{alertstate="firing"}) / sum(ALERTS_FIRED) * 100
# 告警响应时间趋势(上升趋势说明告警被忽略)
avg_over_time(ALERTS_ACK_TIME[7d])
告警分级策略
不是所有告警都需要叫醒人。建立三层告警分级:
| 层级 | 通知方式 | 触发条件 | 示例 |
|---|---|---|---|
| Page | 电话 + 短信 | 影响 SLO 的事故 | 服务不可用、错误率超阈值 |
| Ticket | 工单系统 | 需处理但不紧急 | 磁盘水位 80%、单节点故障 |
| Info | Slack/钉钉 | 仅需知晓 | 部署完成、扩容触发 |
# 告警路由配置示例(Alertmanager)
route:
receiver: 'default'
group_by: ['alertname', 'cluster']
group_wait: 10s # 首次告警等待时间
group_interval: 30s # 同组告警间隔
repeat_interval: 4h # 重复通知间隔
routes:
# P0/P1:立即电话通知
- matchers: ['severity=~"P0|P1"']
receiver: 'pagerduty-critical'
group_wait: 0s
repeat_interval: 30m
# P2:工作时间通知
- matchers: ['severity="P2"']
receiver: 'slack-alerts'
group_wait: 30s
repeat_interval: 2h
active_time_intervals:
- business-hours
# P3/P4:仅记录
- matchers: ['severity=~"P3|P4"']
receiver: 'null'
receivers:
- name: 'pagerduty-critical'
pagerduty_configs:
- routing_key: '<key>'
severity: critical
send_resolved: true
- name: 'slack-alerts'
slack_configs:
- api_url: '<webhook-url>'
channel: '#alerts-prod'
send_resolved: true
- name: 'null'
time_intervals:
- name: business-hours
time_intervals:
- weekdays: ['monday:friday']
times:
- { start_time: '09:00', end_time: '18:00' }
降噪策略
1. 告警聚合
同一根因触发的多个告警应聚合为一个:
# Alertmanager 分组配置
route:
group_by: ['cluster', 'service'] # 按 cluster + service 分组
group_wait: 10s # 等待 10s 收集同组告警
group_interval: 30s
2. 抑制规则
当高级别告警触发时,抑制低级别告警:
# Alertmanager 抑制规则
inhibit_rules:
# 当 P0 服务不可用时,抑制该服务的 P2 延迟告警
- source_matchers: ['severity="P0"', 'alertname="ServiceDown"']
target_matchers: ['severity="P2"', 'alertname="HighLatency"']
equal: ['service']
# 当节点宕机时,抑制该节点上的所有应用告警
- source_matchers: ['alertname="NodeDown"']
target_matchers: ['severity=~"P2|P3"']
equal: ['node']
3. 告警 SLO
给告警本身设定 SLO,定期审查:
# 告警质量 SLO
alert_quality_slo:
precision: 0.95 # 95% 的告警是真实问题(误报率 < 5%)
recall: 0.95 # 95% 的真实故障被告警检出
weekly_volume: 20 # 每周告警总量不超过 20 条
ack_time_p90: 5m # 90% 的 P0/P1 告警在 5 分钟内确认
4. 定期告警审计
#!/bin/bash
# 每月告警审计脚本:找出噪声最大的告警规则
# 适用于 Prometheus + Alertmanager
ALERTMANAGER_API="http://alertmanager:9093/api/v2"
echo "=== 月度告警审计报告 ==="
echo "统计周期: $(date -d '30 days ago' +%Y-%m-%d) ~ $(date +%Y-%m-%d)"
echo ""
# Top 10 噪声告警(触发次数最多但未触发升级的告警)
echo "=== Top 10 噪声告警 ==="
curl -s "$ALERTMANAGER_API/alerts" | \
jq -r '.[] | .labels.alertname' | \
sort | uniq -c | sort -rn | head -10
echo ""
echo "=== 从未确认的告警(疑似误报) ==="
curl -s "$ALERTMANAGER_API/alerts?active=true" | \
jq -r '.[] | select(.status.state != "suppressed") | .labels.alertname' | \
sort | uniq -c | sort -rn | head -10
echo ""
echo "=== 建议处理 ==="
echo "1. 触发次数 >50 且无需升级的告警 → 考虑降噪或降级为 Ticket"
echo "2. 从未确认的告警 → 审查是否为误报,考虑删除"
echo "3. 同一时间段集中触发的告警 → 考虑聚合或加抑制规则"
总结
事件管理与 On-Call 体系是 SRE 的核心实践,其设计目标是:
| 维度 | 设计目标 |
|---|---|
| 事件分级 | 量化影响,合理分配响应资源 |
| On-Call 轮值 | 可持续、有主备、尊重个人生活 |
| 事故响应 | 先止血后治病,结构化流程 |
| Postmortem | Blameless,聚焦系统改进 |
| 告警治理 | 精准、可行动、不疲劳 |
核心原则:On-Call 的终极目标不是"更快地救火”,而是"让火越来越少"。每一次故障都是改进系统的机会,每一次 Postmortem 都应该产出减少未来故障的 Action Item。当你的告警从每周 50 条降到每周 5 条,而且每一条都值得响应时,你的 On-Call 体系才算是真正成熟的。
关于 SRE 的度量体系基础——SLI/SLO 与错误预算,可以参考上一篇文章。
参考资料与致谢
本文在撰写过程中参考了以下资料,感谢原作者的贡献:
- Google SRE Book - Managing Incidents — Google SRE 团队,参考了Google SRE Book - Managing Incidents相关内容
- Google SRE Book - Postmortem Culture — Google SRE 团队,参考了Google SRE Book - Postmortem Culture相关内容