止血、复盘与高频案例合集

事故时间线与止血动作

本章是这套排查手册的收口:前面各章讲“怎么定位”,这一章讲“什么时候动、动什么、动完怎么把经验留下来”。所有故障处置都可以压缩成一句话——先用最可逆的手段恢复可用性,再回头找根因。

止血手段优先级

优先级手段适用条件生效速度风险
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 -idf -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。
  • 处置:摘除该实例并优化慢查询,同时临时扩容其他实例承接流量。
  • 预防:按上游实例维度对响应时间设告警,发布时优雅停机,不把调大超时当修复手段。

小结:止血按“回滚 > 摘流量与降级 > 限流 > 扩容 > 改配置”的顺序选择最可逆的手段,先恢复再定位并记录每一步动作;复盘只对事不对人,每条改进项都要有负责人与期限;高频故障的链路高度相似——先看变更与容量水位,再顺着依赖链找到那个“最先慢下来”的组件。

笔记加载中…