止血、复盘与高频案例合集
本章是这套排查手册的收口:前面各章讲“怎么定位”,这一章讲“什么时候动、动什么、动完怎么把经验留下来”。所有故障处置都可以压缩成一句话——先用最可逆的手段恢复可用性,再回头找根因。
止血手段优先级
| 优先级 | 手段 | 适用条件 | 生效速度 | 风险 |
|---|---|---|---|---|
| 1 | 回滚 | 故障紧跟发布、配置或 DDL 变更,且变更可逆 | 分钟级 | 新功能回退;涉及数据订正时需单独评估 |
| 2 | 摘流量 / 切流 | 单实例、单机房、单分区异常,剩余容量有余量 | 秒级 | 剩余容量被打满,操作前必须确认水位 |
| 3 | 降级 | 非核心依赖故障,产品已约定降级行为 | 秒级 | 功能不可用,需要有人拍板 |
| 4 | 限流 | 流量超出容量且无法快速扩容 | 秒级 | 部分正常用户被拒,须按业务优先级放行 |
| 5 | 扩容 | 容量真实不足,且扩容路径验证过 | 分钟级 | 成本上升,且会掩盖代码缺陷 |
| 6 | 改配置 / 重启 | 无其他手段时的兜底 | 分钟级 | 现场丢失,根因可能被掩盖并再次复发 |
顺序不是教条,但逻辑是硬的:越靠前的手段越可逆、越不依赖对根因的理解。反过来做(先重启再查)等于先把证据删掉,再开始破案。
变更冻结与“先恢复再定位”
- 故障期间冻结一切与恢复无关的变更:新版本发布、配置调整、数据订正全部暂停。
- 定位阶段每一次操作都要记录时间和命令,否则复盘时无法区分“故障现象”和“我们自己改出来的现象”。
- 服务恢复后不要立刻收工:先抓一份完整体检(指标、日志、连接、线程栈),再开始写复盘。
灰度与开关的价值
| 手段 | 作用 | 前提 |
|---|---|---|
| 灰度发布 | 把故障影响面限制在 1% ~ 5% 的流量 | 具备按用户或流量比例切分的能力 |
| 功能开关 | 出问题时秒级关掉功能,无需回滚代码 | 代码预留开关,且开关本身被测过 |
| 降级开关 | 依赖故障时走本地缓存或默认值 | 降级后的行为产品可接受 |
| 开关的兜底 | 防开关系统自身故障导致无法操作 | 保留本地配置文件或环境变量兜底 |
能在 10 秒内关掉的开关,价值高于任何一个性能优化。建设顺序上,先有开关和灰度,再谈自动化扩容。
事故分级与沟通
| 级别 | 判定标准 | 沟通节奏 |
|---|---|---|
| P0 | 核心链路全量不可用或发生数据丢失 | 立即拉起语音,每 5 分钟同步 |
| P1 | 核心功能部分不可用,影响面大于 10% | 每 15 分钟同步 |
| P2 | 非核心功能异常,或已有降级方案 | 每 30 分钟同步 |
| P3 | 性能下降但功能可用 | 工单跟踪 |
角色必须提前定好,故障发生时再分工一定乱:
- 指挥(1 人):决定止血方案与升降级,不亲自排查;
- 排查(1 ~ 2 人):执行定位命令并输出结论;
- 执行(1 人):执行变更与回滚,避免多人同时操作生产;
- 记录(1 人):记录时间线与动作,这是复盘时最值钱的素材。
对外沟通只说三件事:影响面、当前状态、下次同步时间。不猜根因,不承诺恢复时间。
复盘模板
1. 事件概要:一句话说清影响——哪些用户、哪些功能、持续多久
2. 时间线:发现 → 响应 → 止血 → 恢复,每个动作精确到分钟,写明谁做的
3. 影响面:受影响请求量、用户数、金额、是否有数据不一致
4. 根因:直接原因 + 促成条件(为什么会发生、为什么监控没拦住)
5. 处置有效性:哪些动作有用、哪些无效、为什么慢了
6. 改进项:每项都必须有负责人与期限,分为立即修复、本迭代、长期建设
7. 未解问题:暂时无法定论的部分,写清后续验证方式
复盘的三条纪律:对事不对人,根因分析的目的是找“系统为什么允许这个错误发生”,不是找谁背锅;改进项必须可验证,写“加强监控”是空话,写“为 /order/create 的 P99 增加 500ms 阈值告警,负责人 X,本周五前完成”才可跟踪;改进项少于三项,通常意味着根因分析停在了表面。
高频案例合集
案例一:接口 P99 突增但错误率为 0
- 现象:某接口 P99 从 80ms 涨到 2s,QPS 与错误率都正常。
- 定位:
top看资源正常 → 应用日志显示线程在等待 → 火焰图显示时间集中在 JSON 序列化 → 定位到本次发布新增了一个大字段。 - 根因:响应体变大,序列化与网络传输耗时上升,拖慢整体 P99。
- 处置:回滚该发布,字段改为按需返回。
- 预防:序列化耗时与响应体大小纳入监控,接口评审关注返回字段体积。
案例二:Redis 超时引发数据库雪崩
- 现象:Redis 出现超时后,数据库连接池瞬间打满,全站返回 500。
- 定位:
redis-cli --latency-history -i 5看到周期毛刺 → 命中率骤降 →evicted_keys持续增长 → 确认内存打满正在淘汰热数据。 - 根因:内存不足触发淘汰,缓存命中率下降,请求全部回源数据库。
- 处置:临时扩容 Redis,核心接口加本地缓存兜底,数据库侧限流保护。
- 预防:内存水位与命中率双告警;缓存不可用时必须有降级路径,宁可返回旧值也不打数据库。
案例三:MySQL 死锁导致订单失败
- 现象:下单接口偶发失败,错误信息含
Deadlock found when trying to get lock。 - 定位:
SHOW ENGINE INNODB STATUS查看最近一次死锁的两条事务语句与锁等待,确认加锁顺序相反。 - 根因:同一批数据在不同事务中的加锁顺序不一致。
- 处置:统一按主键排序后再更新,缩短事务,应用侧对死锁错误做有限重试。
- 预防:事务内不做远程调用与大批量操作,死锁次数纳入监控。
案例四:磁盘 inode 满
- 现象:服务无法写日志和上传文件,报
No space left on device,但df -h显示空间还有余量。 - 定位:
df -i看到 inode 使用率 100% →du -sh /var/log/*与文件数量统计定位到具体目录。 - 根因:大量小文件(会话文件、未轮转日志碎片)耗尽 inode,而非磁盘空间不足。
- 处置:清理过期小文件,修正日志轮转与临时文件清理策略。
- 预防:
df -i与df -h一起巡检,小文件目录加定期清理任务。
案例五:数据库连接池耗尽
- 现象:接口大面积超时,错误为
Connection is not available, request timed out。 - 定位:连接池监控 active 顶到最大值 → 数据库
SHOW PROCESSLIST看到长时间运行的查询 → 定位到一条缺索引的 SQL。 - 根因:慢 SQL 长时间占用连接,池子被耗尽,正常请求拿不到连接。
- 处置:终止慢查询并补索引;临时放宽连接池上限,同时设置获取连接超时做到快速失败。
- 预防:慢查询与连接池 active 水位告警;池满时快速失败而不是无限等待。
案例六:MQ 堆积导致业务延迟
- 现象:订单状态更新延迟数十分钟,Lag 持续上升。
- 定位:消费速率明显下降 → 消费者日志显示调用三方接口超时 → 三方接口 P99 从 200ms 涨到 5s。
- 根因:外部依赖变慢,消费线程被占满,消费能力骤降。
- 处置:临时扩容消费者,缩短三方调用超时,非核心消息转死信延后处理。
- 预防:消费端超时预算与隔离舱,Lag 按预计消化时间告警。
案例七:K8s Pod 反复重启
- 现象:Pod 每 3 分钟重启一次,状态为 CrashLoopBackOff。
- 定位:
kubectl describe pod显示Last State: Terminated, Reason: OOMKilled, Exit Code: 137,limit 为 512Mi。 - 根因:JVM 未感知容器内存限制,默认堆按宿主机内存计算,超过 limit 被内核杀掉。
- 处置:设置
-XX:MaxRAMPercentage=70,把 limit 调整到真实用量的 1.3 倍左右。 - 预防:所有 JVM、Go、Node 服务按容器限制显式配置内存参数,重启与 OOMKilled 次数告警。
案例八:Nginx 504 上游慢
- 现象:Nginx 出现大量 504,用户侧提示服务暂时不可用。
- 定位:
awk '{print $9}' access.log | sort | uniq -c | sort -rn确认 504 占比 → 按$upstream_addr聚合发现集中在一台实例 → 该实例线程池打满,日志显示单次查询耗时 8s。 - 根因:单实例因慢查询导致线程耗尽,
proxy_read_timeout到期返回 504。 - 处置:摘除该实例并优化慢查询,同时临时扩容其他实例承接流量。
- 预防:按上游实例维度对响应时间设告警,发布时优雅停机,不把调大超时当修复手段。
小结:止血按“回滚 > 摘流量与降级 > 限流 > 扩容 > 改配置”的顺序选择最可逆的手段,先恢复再定位并记录每一步动作;复盘只对事不对人,每条改进项都要有负责人与期限;高频故障的链路高度相似——先看变更与容量水位,再顺着依赖链找到那个“最先慢下来”的组件。