真实事故复盘剧本:从指标抖动到根因的全链路追查
凌晨 2:14。值班手机响了。PagerDuty 上一条告警:checkout 服务的 SLO Burn Rate 在过去 5 分钟内飙升到了 14.4 倍。SLO Dashboard 显示 p99 延迟从 120ms 飙升到了 2.3s,错误率从 0.05% 涨到了 3.2%。
这是一个真实但被高度抽象化了的事故复盘剧本。它的目的不是给你讲一个”有趣的事故故事”——而是拆解从”告警响了”到”根因定位了”这条排障链路上,在每一个环节应该看什么信号、用什么工具、做什么判断。这个剧本可以成为团队的 on-call 培训教材和 Game Day 演练脚本。
一、Golden Minute(T+0 — T+5)
T+0min:Ack 告警。确认这不是 false positive——打开 Grafana SLO Dashboard,看一眼 checkout 服务的 p99 延迟是否真的异常。
T+1min:确认影响范围。RED Dashboard——Rate 有没有暴跌?(用户请求进不来 → 可能上游或网络问题)。Errors 有没有同步上升?(有 → 服务正在报错,优先看错误日志)。Duration 飙升但 Rate/Errors 没变?(服务在响应但变慢了——可能是下游依赖慢或资源争用)。
T+2min:Review 最近变更。过去 1 小时内有没有 deploy?有没有 ConfigMap/Secret 变更?有没有基础设施变更(扩缩容、网络规则更新)?60-80% 的生产事故与近期变更有关——这是 Google SRE Book 的核心统计。
T+3min:如果不是 false positive,确定 Incident 角色:Commander(指挥,做决策)、Communications(对内对外沟通)、Ops Lead(执行排障操作)。三人可以兼任(在小团队中),但角色不能混淆——Commander 不应该亲自 debug。
T+5min:发出第一次状态更新(Slack/钉钉):“checkout 服务 p99 latency 升至 2.3s,error rate 3.2%,正在排查。怀疑与下游 Redis 有关。预计 30 分钟内给出初步结论。”
二、Metric 阶段(T+5 — T+15)
先定性二问:是所有实例都慢,还是只有一个实例慢?是只有 checkout 慢,还是它的下游也慢?
按 instance 分解 checkout 的 p99
latency——如果只有一个实例慢,可能是该 node 上有 noisy
neighbor
或节点级故障(CPU/disk/NIC)。如果所有实例都慢——问题在
checkout 服务本身或它的下游依赖。
按 endpoint 分解——是所有 API
端点都慢,还是只有某个特定端点慢?如果只有
/api/checkout/submit 慢而
/api/checkout/cart 正常——问题在 submit
路径的某个特定下游调用。
看下游依赖的延迟:checkout → Redis、checkout → Payment Service、checkout → Inventory Service。如果 Redis 的 p99 延迟也在飙升——那么 checkout 不是受害者,Redis 才是根因。
三、Trace 阶段(T+15 — T+25)
从 Grafana Dashboard 上点开时间线上的 exemplar,跳转到 Tempo 或 Jaeger。在 Trace 视图中按 latency 排序,找最慢的几条 Trace。
在 Trace View 中逐层看:总延迟 2.3s 是怎么分布的?如果
redis_get_cart span 花了 850ms → 重点怀疑
Redis。如果 payment_service.authorize span 花了
1.5s → Payment 服务是 bottleneck。如果所有子 Span 都是
10-50ms 但 checkout.submit SERVER span 本身有
2s 闲置时间 → 可能是 Checkout 服务内部有锁等待或 GC
暂停。
如果 1% 的头部采样没有覆盖到异常 Trace——临时提高采样率:把 Collector 的尾部采样 threshold 降低到 200ms(当前配置是 500ms),重新等几分钟看是否有更多慢 Trace 被捕获。
四、Log 阶段(T+20 — T+30)
从 Tempo 的 Trace View 点 “Related Logs” 跳转到
Loki——自动按 trace_id
过滤出这条请求关联的所有日志。关键搜索:{service="checkout"} |= "timeout" OR |= "error" OR |= "exhausted"。
如果日志显示 “redis: connection pool exhausted, waiting 843ms”——Redis 连接池不够。可能的原因:连接池配置太小、Redis 服务器响应变慢连接被占满、checkout 有 goroutine 泄漏导致连接不归还。
如果日志没有任何异常——checkout
打日志不够。这就是一个”可观测性盲区”:你的 Trace 告诉你
redis_get_cart 花了
850ms,但你的日志没说为什么。补救措施:在 redis client 的
span event 层加一条”获取连接等待时间”的记录。
五、Profile + 内核阶段(T+30 — T+45)
如果 Log 和 Trace 都指不出明显的根因——打开 Pyroscope/Parca 看过去 15 分钟的 CPU flamegraph 和 heap profile。
CPU flamegraph 显示大部分 CPU 时间花在
encoding/json.Marshal → checkout
服务的响应体太大,JSON 序列化成了瓶颈。 Heap profile
显示内存使用持续爬升 → 可能是内存泄漏,GC 频率在增加 → 打开
runtime.gcDrain 的占比——如果超过 10% CPU → GC
确实是瓶颈。 如果 CPU 和内存都正常——延迟可能在 I/O
等待或内核层面。上 bpftrace:
bpftrace -e 'kprobe:ext4_sync_file { @fsync_lat = hist(nsecs); }'如果 ext4_sync_file 的延迟有罕见的 >
200ms 的采样——冷数据页回写在拖慢 fsync。
六、缓解优先于修复
找到根因之前先做缓解——不要让用户一直等着: - 特定实例慢 → 从 LB 摘掉流量 - 下游 Redis 慢 → 打开 Circuit Breaker(在大面积超时之前主动熔断而不是被动等超时) - 最近部署引入 → Rollback - 容量不够 → 扩容(最粗暴但最快)
修好后持续观察 SLO Dashboard 30 分钟,确认恢复。然后写事后复盘——这次事故暴露了哪些可观测性盲区?告警在用户感知之前触发了吗?排查过程中哪些环节因缺数据而花了大量时间?
七、三个深度不同的排障剧本
剧本 A(初级):MySQL
慢查询引发连锁超时。Metric 发现 p99 飙升 → Trace
发现 mysql_query span 花了 3s → Log 显示
MySQL server has gone away → Profile 确认 CPU
正常(不是 GC)→ 根因:一个没建索引的 SQL 全表扫描导致 MySQL
连接池被长查询占满。
剧本 B(中级):DNS
超时导致服务不可用。Metric 显示错误率飙升但 latency
正常(连接直接失败)→ Trace 没有异常 span(因为 request
根本没进服务)→ Log 没有错误(DNS
是系统调用层,应用没日志)→ 内核追踪
tcp_connect 发现 getaddrinfo 花了
5s → 根因:CoreDNS 的某个上游 DNS
服务器挂了。这个剧本的关键教训:当所有用户态可观测性工具都说”一切正常”时,你要怀疑内核层。
剧本 C(中级):JVM Full GC
隐藏的内存泄漏。Metric 显示周期性的延迟尖峰(每 30
分钟一次)→ Trace
在尖峰时间窗口中显示所有请求都慢了但不是某个下游→ Profile
对比尖峰前后的 heap profile 发现 byte[]
持有量在尖峰前飙升 → 根因:某个 Kafka consumer 的 batch size
配置太大,触发 Full GC。
八、事故响应工具箱清单
- Metric:Grafana RED Dashboard + SLO Burn Rate 面板
- Trace:Tempo/Jaeger 按 latency 排序 + 按 trace_id 精确查找
- Log:Loki 按 trace_id 关联 + 关键字搜索
- Profile:Pyroscope/Parca 对比式火焰图(“尖峰前 15min vs 正常时期”)
- Kernel:bpftrace + perf + ftrace
function_graph - Events:K8s Events(最近 Pod OOM Kill / Liveness Probe Fail / Node Not Ready)
- 变更记录:Git deploy log + K8s
kubectl rollout history+ ConfigMap diff
九、关键概念回顾
- 五阶递进:Alert → Metrics(定性+定范围)→ Traces(具体调用链)→ Logs(具体错误信息)→ Profiles(代码行)→ Kernel Tracing(内核路径)
- 缓解优先:Mitigation before Root Cause Fix。回滚、切流、熔断、扩容——不要等找到根因才行动。
- 每个事故都是一次可观测性审计:事后复盘应该产出一份”这次事故暴露的可观测性盲区清单”。
十、下一步
事故复盘让你看到了自建可观测性栈的能力上限。下一个问题就是:到底是自建还是托管?下一篇 自建 vs 托管:OpenTelemetry 自建栈与 SaaS 的选型决策。
上一篇:中国可观测性厂商对比
下一篇:自建 vs 托管:OpenTelemetry 自建栈与 SaaS 的选型决策
参考资料
- Google, Site Reliability Engineering, Chapter 14: Managing Incidents, O’Reilly, 2016
- PagerDuty, Incident Response Documentation, https://response.pagerduty.com/
- J. A. Scott, Incident Management for Operations, O’Reilly, 2013
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
可观测性工程
从 Metrics、Logs、Traces 到 Profiling、eBPF、OpenTelemetry 与 SLO 治理,面向中国工程团队的可观测性系统化手册。全 25 篇。
【可观测性工程】可观测性全景:Metrics、Logs、Traces、Profiles、Events 五大支柱
从控制论到云原生:拆解可观测性的五大信号支柱,对比监控与可观测性的本质区别,梳理开源/商业/SaaS 分类,以及国内互联网公司三大支柱落地现状与典型工程坑点。
【可观测性工程】指标体系设计:USE、RED、Golden Signals 与业务 KPI
USE 方法论适用于资源,RED 方法论适用于请求,Golden Signals 适用于服务——三套方法论各有其适用对象。本文从 Brendan Gregg、Tom Wilkie、Google SRE 的原始定义出发,构建覆盖资源→服务→业务的完整指标体系,并给出 Prometheus 命名规范、基数治理策略与可抄的指标清单。
【可观测性工程】OpenTelemetry 深入:SDK、Collector、语义约定与版本演进
从 OpenTracing 与 OpenCensus 合并到今天的 OTel v1 稳定版,梳理 SDK 生命周期、Collector 流水线、OTLP 协议与 Semantic Conventions 的工程意义,并结合阿里 ARMS、观测云、夜莺等国内实践,给出多租户与尾采样的落地建议。