2024 年有一次 P1 故障,我们花了 40 分钟才把所有相关人拉进会议,而故障本身 20 分钟就修好了。那 40 分钟里,销售团队在群里问进度,CEO 在私信我,技术 VP 在问根因,我一边排查一边回消息,最后谁都没说清楚。
这次故障之后,我们系统性地建立了故障管理流程。核心洞察是:技术处理和协调沟通必须并行,但必须由不同的人承担。
故障定级标准#
定级是故障管理的起点,决定了后续的响应力度和沟通范围。我们用影响面和严重程度两个维度定级:
定级矩阵#
| 级别 | 用户影响 | 营收影响 | 响应时效 | 通知范围 |
|---|---|---|---|---|
| P1 | 核心功能完全不可用(> 10% 用户受影响) | 直接损失 > 10k/小时 | 立即响应,全员 | CTO、VP、全团队 |
| P2 | 核心功能部分降级,有可用替代路径 | 间接影响,难以量化 | 30 分钟内 | 团队负责人、SRE |
| P3 | 非核心功能异常,有 workaround | 无明显影响 | 工作时间内 | 值班工程师 |
P1 的判断原则:宁可误报,不能漏报。不确定是 P1 还是 P2 时,先按 P1 处理,升级成本远低于漏报代价。
快速定级决策树#
告警触发
├── 支付/登录/核心业务接口不可用?→ P1
├── 错误率 > 5% 且持续 > 2min?→ P1
├── 数据库/消息队列等基础设施完全不可用?→ P1
├── 错误率 1%-5% 或部分功能降级?→ P2
├── 单用户投诉 / 边缘功能异常?→ P3
└── 不确定?→ 先 P1,处理中降级
Incident Commander:为什么不能一人兼三职#
故障响应中有三个核心角色,必须分清楚:
Incident Commander(IC):故障整体协调人,负责推进响应节奏、协调资源、决定升级时机。IC 不一定技术最强,但必须有决策权和全局视角。IC 的职责是确保事情在推进,而不是自己去干。
技术负责人(Tech Lead):实际排查和修复故障的工程师,专注在技术操作,不承担对外沟通职责。
沟通负责人(Comms):向外部(用户、客户、管理层)和内部(其他团队)输出状态更新,屏蔽技术人员被打扰。
为什么不能一人兼三职?
工程师的大脑在压力下无法高效切换任务。排查故障需要深度思考,任何打断都会造成"上下文切换成本"。我们测算过,一次 Slack 消息打断需要约 8 分钟才能重新进入专注状态。P1 故障的前 30 分钟,技术负责人被打断一次,可能就是多 15 分钟的恢复时间。
小团队怎么办?
人手不够时,IC 可以兼 Comms,但绝对不能兼 Tech Lead。IC 的核心价值是保证技术人员能专心干活。
响应 Checklist:前 5 分钟#
P1 故障的前 5 分钟是混乱的高峰期,Checklist 能防止遗漏。
IC 执行:
- 确认告警是真实的(不是监控自身问题)
- 初步评估影响范围(哪些用户、哪些功能)
- 在 #incident 频道发起故障线程
- 确认技术负责人已接手
- 确认沟通负责人已就位
- 评估是否需要立即通知外部用户
技术负责人执行:
- 打开监控面板,确认故障范围
- 检查是否有近期变更(部署、配置、流量变化)
- 开始定界(见下节)
沟通负责人执行:
- 向管理层发送初始通知(仅确认在处理,不报根因)
- 如有 Status Page,更新状态为
Investigating - 准备用户通知草稿(等 IC 确认后发出)
15 分钟定界框架#
定界(Scoping)是找到故障边界:什么是坏的,什么是好的,问题出在哪一层。目标是 15 分钟内完成初步定界,给技术团队和管理层都一个方向。
按层级从外到内排查:
第一层:网络层(2 分钟)#
# 外部连通性
curl -w "%{http_code} %{time_total}s" -o /dev/null https://api.example.com/health
# DNS 解析
dig api.example.com +short
# 负载均衡健康检查(以 AWS ALB 为例)
aws elbv2 describe-target-health --target-group-arn arn:aws:... --region us-west-2
# K8s Service / Ingress 状态
kubectl get ingress -A
kubectl get svc -n production
如果外部请求直接超时,且 DNS 正常,问题可能在 LB 或 Ingress 层。
第二层:应用层(3 分钟)#
# Pod 状态总览
kubectl get pod -n production -o wide | grep -v Running
# 最近的重启情况
kubectl get pod -n production -o custom-columns=NAME:.metadata.name,RESTARTS:.status.containerStatuses[0].restartCount | sort -k2 -n -r | head -10
# 快速查看错误日志
kubectl logs -n production deploy/api-service --tail=50 | grep -i "error\|panic\|fatal"
# 检查 HPA 状态(是否在扩缩容中)
kubectl get hpa -n production
第三层:数据层(3 分钟)#
# 数据库连接测试(从应用 Pod 内部)
kubectl exec -n production deploy/api-service -- nc -zv mysql-master.data 3306
# 检查连接池状态(以 Go 为例,需应用暴露 /debug/vars 或 metrics)
curl http://api-service:9090/metrics | grep db_pool
# Redis 连通性
kubectl exec -n production deploy/api-service -- redis-cli -h redis-master ping
# 检查慢查询(MySQL)
kubectl exec -n data deploy/mysql -- mysql -u root -p'password' -e "SHOW PROCESSLIST;" | grep -v Sleep
第四层:基础设施层(3 分钟)#
# 节点状态
kubectl get node -o wide
# 节点资源压力
kubectl top node
# 存储 PVC 状态
kubectl get pvc -A | grep -v Bound
# 最近发生的事件
kubectl get events -A --sort-by=.lastTimestamp | tail -30
定界结论模板:
[定界结论] 2026-04-12 03:25
问题层:数据层(MySQL 连接池耗尽)
正常层:网络层、应用层(Pod 运行正常)
影响范围:所有需要写入的 API(/api/v1/payment, /api/v1/order)
已排除:网络问题、部署变更、K8s 基础设施
下一步:扩大连接池限制或重启数据库
沟通模板#
内部状态更新(每 15 分钟一次)#
[P1 更新] 2026-04-12 03:30 | 进行中 | 已持续 18 分钟
现状:MySQL 连接池耗尽,支付接口全部返回 500
进展:DBA 已介入,正在评估连接池扩容方案
预计恢复:30 分钟内(预计 04:00 前)
临时缓解:已关闭非必要写操作,降低数据库压力
下次更新:15 分钟后或状态有变化时
外部用户通知#
原则:简单、诚实、有时间预期,不解释技术细节。
我们正在处理一个影响支付功能的问题。部分用户在完成支付时可能遇到错误。
我们的团队已在处理中,预计在 [时间] 前恢复正常。感谢您的耐心等待。
不要写:由于数据库连接池参数配置不当导致...(用户不需要知道这些,而且写出来后面可能变)
Post-Mortem 模板#
Post-Mortem 的价值在于系统性改进,不是追责。Blameless 文化的落地要点:描述发生了什么,不描述谁做了什么错误决定。
# Post-Mortem:支付服务不可用事件
**日期**:2026-04-12
**级别**:P1
**持续时间**:23 分钟(03:12 - 03:35)
**撰写人**:[姓名]
**状态**:待审核
## 影响
- 支付接口 100% 不可用,持续 23 分钟
- 受影响用户:约 2,400 次支付请求失败
- 估计营收影响:约 ¥85,000
## 时间线
| 时间 | 事件 |
|------|------|
| 03:10 | MySQL 连接池达到上限(100/100),新请求开始排队 |
| 03:12 | 错误率超过 5%,PagerDuty 告警触发 |
| 03:13 | 值班工程师 Acknowledge |
| 03:15 | 定位到数据库连接问题 |
| 03:17 | 支付服务切换到只读降级模式 |
| 03:19 | DBA on-call 介入 |
| 03:28 | MySQL 连接池配置更新,Pod 重启 |
| 03:33 | 错误率回落到 < 0.1% |
| 03:35 | 宣布故障解除,支付恢复正常 |
## 根因
MySQL 连接池上限设置为 100,上个 Sprint 支付服务扩容到 8 个 Pod,每 Pod 最多 15 个连接,峰值需求为 120 个连接,超出数据库允许的上限。
流量高峰(凌晨 3 点的定时任务批量处理)触发了连接池耗尽。
## 贡献因素
1. 扩容时没有同步更新数据库连接池配置
2. 数据库连接池使用量没有告警(只有 Pod 数量告警)
3. 没有针对连接池接近上限的 Runbook
## 改进措施
| # | 措施 | 类型 | 责任人 | 截止日期 |
|---|------|------|-------|---------|
| 1 | 为 MySQL 连接池使用率添加告警(> 80% 告警,> 90% P1)| 监控 | @张三 | 2026-04-19 |
| 2 | 扩容 Checklist 中增加数据库连接池容量评估 | 流程 | @李四 | 2026-04-19 |
| 3 | 支付服务 Runbook 增加连接池耗尽处理步骤 | 文档 | @王五 | 2026-04-16 |
| 4 | 将连接池配置外部化(通过 Nacos 管理),支持不重启更新 | 工程 | @赵六 | 2026-05-01 |
## 做得好的地方
- 定界速度快,3 分钟确认数据库层问题
- 降级方案(只读模式)执行有效,阻止了问题继续扩大
- DBA on-call 响应及时
## 经验教训
扩容不只是加 Pod,需要系统性评估所有依赖资源的容量。
Post-Mortem Review 会议#
写完不开会等于白写。Post-Mortem 需要在故障后 48-72 小时内完成 Review:
- 参会人:IC、技术负责人、相关团队 Lead
- 时长:30-60 分钟
- 目标:确认根因无异议、Action Items 有 owner 和截止日期
- 禁止:追责性言论(“为什么你没有…")
改进追踪:让 Action Items 真正落地#
Post-Mortem 变成走过场的最大原因:Action Items 没有 SMART 属性,没人跟进。
SMART 写法对比:
| 糟糕的写法 | SMART 写法 |
|---|---|
| 改进监控 | 2026-04-19 前,@张三 为 MySQL 连接池添加 80% 使用率告警,在 Grafana payment 面板新增连接池面板 |
| 优化扩容流程 | 2026-04-19 前,@李四 在服务扩容 Runbook 中增加数据库连接容量评估步骤,并在下次扩容时验证 |
我们用 Jira 管理 Action Items,Post-Mortem 的每一条改进措施都创建为 story,挂在对应 Sprint。每周 SRE 会议固定 5 分钟 review 未关闭的 Post-Mortem action items。
30 天不关闭的 Action Item,会进入工程健康度报告,在季度复盘中说明原因或重新排期。
真实案例:数据库连接池耗尽的完整 IM 过程#
这就是上面 Post-Mortem 对应的真实故障处理记录。几个关键时刻值得复盘:
为什么 23 分钟就解决了?
- IC(我)没有参与排查,专注协调:通知 DBA、每 5 分钟在频道更新状态、屏蔽管理层的直接询问
- 技术负责人有 Runbook,数据库连接排查步骤清晰,3 分钟就定界到数据层
- 降级方案(只读模式)是预案,不是临时想出来的,执行很快
如果没有这套流程会怎样?
参照之前的经验:一个人又排查又沟通,大概率 1 小时内不会结束,期间管理层和产品会不断问进度,排查时间线会更长。
常见陷阱#
陷阱 1:把 Post-Mortem 变成审判
“谁让你部署的”、“当时为什么没想到”——这类问题会让工程师下次故障时不愿意如实记录。Blameless 不是免责,是把注意力放在系统问题而不是个人失误上。
陷阱 2:故障解决了就算了,不写 Post-Mortem
P2 以上故障,无论多忙,都必须写 Post-Mortem。我们的最低要求:时间线 + 根因 + 至少 1 条 Action Item。其他部分可以简化,但这三样不能省。
陷阱 3:IC 参与技术排查
IC 一旦"下水"开始自己排查,就没人做协调了。技术 Lead 需要 IC 拍板决策时,IC 参与决策;技术细节排查,IC 不参与。
陷阱 4:定界没有时限
“我们再看看”——这句话在故障处理中是危险的。每个排查方向给固定时间(3-5 分钟),时间到没有结论就换方向或升级。大多数故障的根因在 15 分钟内能确定层次,不能定界的情况应该触发升级。
故障处理能力是练出来的。从定级开始,到 IC 轮换、Post-Mortem Review,每次故障都是一次往前迭代的机会——真的不要浪费。






