复盘会上一份时间线写着"14:02 开始异常,14:35 恢复"。有人问了一句"你确定是 14:02 开始的吗",回答是"第一个告警是 14:02"。而用户的投诉记录里,最早的抱怨时间是 13:41。
告警开始的时间不是故障开始的时间,它只是"系统终于被看见"的时间。
这也解释了一个常见错位:复盘的讨论大多集中在"恢复用了多久",而改进空间大多在"发现用了多久"。恢复时间往往受限于技术动作(回滚、扩容),而发现时间受限于观测设计——后者更容易改。
而这两个时间的差,恰恰是发现能力的度量——它通常比恢复时间更值得优化。
1. 时间线的五种来源
| 来源 | 精度 | 偏差方向 |
|---|---|---|
| 用户投诉/客服工单 | 高(真实影响) | 早于监控 |
| 业务指标(订单量、成功率) | 中高 | 等于真实影响 |
| 资源指标(CPU、连接数) | 中 | 可能早于或晚于 |
| 应用日志(错误、超时) | 高 | 取决于采样 |
| 变更记录(发布、配置) | 高(精确时刻) | 只在有人为动作时 |
第一项与第三项的差是最有价值的信息:它说明"从影响发生到被监控发现"这段有多长。
这里有个统计上的陷阱:如果一天发 20 次版,那么任何时刻都距某次发布不到一小时。所以"故障前有发布"这件事的信息量,取决于发布频率,不取决于时间接近。
第五项要用作"嫌疑清单"而不是"结论":变更时刻与故障时刻接近只说明可疑,不说明因果。断言因果需要"回滚后恢复"或"同样条件下复现"。
2. 重建时间线的四步
| 步骤 | 动作 |
|---|---|
| 1 定锚点 | 找一个精度最高的时刻(如发布记录) |
| 2 按来源分层 | 用户侧、业务侧、资源侧分别排 |
| 3 对齐时区与时钟 | 全部转 UTC 再比较 |
| 4 标注不确定性 | 每个时刻写精度 |
对齐时还要注意"夏令时":有过夏令时的地区,同一本地时间在一年内出现两次或不存在,于是按本地时间排序会得到错乱的顺序。全部转 UTC 能彻底避免这类问题。
第三项是最容易出错的技术细节:日志按本地时间、监控按 UTC、客服系统按用户时区,三者未对齐时会出现"故障发生在监控之后"这种物理不可能。
第四项是诚实性要求:"约 13:40"与"13:41:22"是不同的信息,混在一起会让读者以为所有时刻都同样精确。
// 重建后的时间线(每行带来源与精度)
13:41 ~ 客服收到首批投诉(精度:分钟,来源:工单系统)
13:44 ~ 下单成功率从 99.2% 跌到 71%(精度:分钟,来源:业务指标)
13:47 ~ 支付服务错误率升至 12%(精度:分钟,来源:APM)
13:52 ~ 连接池使用率达到 98%(精度:秒,来源:资源监控)
14:02 ! 首个 P1 告警触发(精度:秒,来源:告警系统)
14:06 ! 值班确认并开始排查(精度:分钟,来源:IM 记录)
14:31 ! 回滚支付服务 v2.31 → v2.30(精度:秒,来源:发布系统)
14:35 ! 成功率回到 99%(精度:分钟,来源:业务指标)
// 关键读数:影响开始 13:41 → 告警 14:02 = 21 分钟发现延迟
3. 发现延迟怎么算、怎么改
| 环节 | 可优化手段 |
|---|---|
| 影响发生 → 业务指标可见 | 缩短指标采集粒度 |
| 指标可见 → 告警触发 | 改阈值/改用 SLO 告警 |
| 告警触发 → 人确认 | 值班与通知链路 |
| 人确认 → 定位 | 缩短排查路径 |
第二项还有一个"方向"上的选择:症状告警(用户受影响)适合告警,原因指标(资源、队列)适合看板。把原因指标也做成告警,会得到大量"资源满了但用户没感觉"的噪音。
第一项常被忽略:业务指标按 5 分钟聚合,就注定有 5 分钟盲区。而很多团队只看资源指标(1 分钟粒度),于是"影响已经发生但资源正常"的故障完全看不见。
第二项的关键是从阈值告警转向"症状告警":阈值告警盯的是"某个数超了",症状告警盯的是"用户受影响了吗"。前者误报多、后者更贴近真实。
// 症状告警:直接盯用户可感知的结果
alert: checkout_success_rate
expr: rate(checkout_ok[5m]) / rate(checkout_total[5m]) < 0.95
for: 2m
severity: P1
annotations:
summary: "下单成功率低于 95%(当前 {{ $value }})"
runbook: "https://.../runbooks/checkout"
# 关键:用业务结果而不是资源指标做判据
4. 时钟与对齐
| 问题 | 后果 |
|---|---|
| 主机时间漂移 | 跨机日志顺序错乱 |
| 时区混用 | 出现"负时间" |
| 应用时间与采集时间不同 | 归因错位 |
| 精度的假象 | 毫秒值实际是秒级近似 |
第四项(精度假象)有个典型例子:日志时间精确到毫秒,但实际精度只有秒(因为时间戳来自秒级缓存的时钟源)。于是两条"相差 3ms"的记录,真实顺序可能是反的——用它做因果推断会得出错误结论。
第一项在多节点部署里普遍存在:NTP 未同步时,两台机器的日志可能差几十秒,而分布式追踪恰恰依赖跨机时间戳排序。所以"trace 里出现了子调用早于父调用"是时钟问题的典型信号。
第三项要区分两个时间:事件发生时间(应用写)与采集时间(代理写)。用采集时间做时间线,会把"日志积压"误读成"故障延后"。
时间线还有一个"下钻"技巧:从影响开始时刻往前推 15 分钟,看有什么变化。多数故障的起点与某个变更在同一窗口内,而"往前推"这个动作能避免只盯着告警之后的时间。
// 变更与异常对齐:把最近 3 小时的发布/配置变更列出来
git log --since="3 hours ago" --pretty='%h %ad %s' --date=iso
kubectl rollout history deploy/pay -o json | jq -r '.items[-5:][] | "\(.revision) \(.changeCause)"'
// 与时间线的锚点对齐,逐条判断"时间接近且因果可信"
5. 时间线在复盘里的用法
| 用法 | 说明 |
|---|---|
| 定位决策点 | 哪些时刻做了判断、依据是什么 |
| 量化延迟 | 发现、响应、恢复三段 |
| 暴露信息断点 | 哪一段"没人知道发生了什么" |
| 支撑改进项 | 每个改进对应一个延迟 |
第三项最有价值:时间线里出现"空白段"时,通常对应"没人知道该看什么"。这一段是流程或观测的缺口,而不是运气不好。
第四项要求每个改进项挂在一个具体延迟上,否则改进会变成"加强监控"这类无法验收的口号。
落地的记录载体也要固定:时间线应当写在故障单里,而不是散在 IM 聊天记录。因为复盘时需要所有人看同一份,而聊天记录的顺序会被消息插入打乱,也不便标注精度。
// 故障单里的时间线表(五列,可追加)
时刻(UTC) 事件 来源 精度 备注
13:41 首批投诉 工单系统 分钟 影响起点
14:02 首个 P1 告警 告警系统 秒 =
14:31 回滚 v2.31→v2.30 发布系统 秒 变更
14:35 成功率恢复 业务指标 分钟 恢复判据
6. 排错:五种"时间线不可信"
| 现象 | 成因 | 处理方向 |
|---|---|---|
| 故障晚于恢复 | 时区未对齐 | 全部转 UTC |
| 告警早于影响 | 资源指标先动 | 区分症状与原因 |
| 相邻事件顺序颠倒 | 时钟漂移 | 检查 NTP |
| 找不到起点 | 只有告警记录 | 补业务指标与投诉 |
| 各人回忆不一致 | 无统一记录 | 以系统记录为准 |
第三项(空白段)还应当反向使用:如果某一整段都"没人知道",那这段应当出现在改进清单的第一条,因为它是下一次故障里最容易重复的部分。
第五项是复盘常见冲突源:人的回忆会被事后信息污染(知道了原因就会"记得"当时已经怀疑它)。所以时间线应当以系统记录为主,人的回忆只用于补充"当时在想什么"。
时间线还有一条容易漏的"末端":恢复不等于"用户不再受影响"。技术上恢复(成功率回到基线)之后,客服仍在处理积压工单、退款仍在批量处理——这些"尾巴"对用户的影响可能持续数小时,而它通常不进时间线。
// 时间线的三个端点,不止两个
影响开始 ─── 技术恢复 ─── 用户感知恢复 ─── 后续清理完成
13:41 14:35 16:10 次日 10:00
// 只写"开始—恢复"会漏掉后两个端点,复盘时低估影响时长
7. 边界:什么情况不必精细重建
| 场景 | 为什么 | 做法 |
|---|---|---|
| 影响极小(个别用户) | 成本高于收益 | 记结论即可 |
| 原因已明确且无争议 | 无需时间线 | 直接写改进 |
| 单一组件、单点故障 | 时间线天然清楚 | 简要记录 |
| 演练而非真实故障 | 时间线由脚本决定 | 记脚本耗时 |
最后一条与"记录成本"有关的边界:时间线不必写到完美,但要写到"能支撑三个数字"——影响开始时间、告警时间、恢复时间。其余细节可以在需要时再补,这三个是复盘的最小骨架。
判断口径一句话:这次复盘要不要回答"为什么没早点发现"。要,就必须有时间线(且必须有用户侧来源);不回答,时间线只是叙事装饰。
8. 自测
- 为什么"告警时间不是故障开始时间"?这个差值度量的是什么能力?
- 变更记录为什么只能当"嫌疑清单"而非结论?断言因果需要什么证据?
- 重建四步里,哪一步最容易出错?"标注不确定性"为什么是诚实性要求?
- 发现延迟分四段,哪一段最常被忽略?为什么业务指标按 5 分钟聚合会造成盲区?
- 为什么"时间线里的空白段"最有价值?改进项为什么必须挂在一个具体延迟上?
- 判断"是否需要精细重建"的那个问题是什么?