三层归因方法论(表象 / 第二根因 / 第一根因 / 超前 2 年)
一句话速记
三层归因让你不只解决表面的 bug,还要追问背后的系统缺陷:表象(用户看到了什么错)→ 第二根因(代码/配置哪里出了 bug)→ 第一根因(为什么这个 bug 能合入主干、为什么监控没发现、为什么测试没覆盖)→ 超前 2 年(整个系统设计上有什么结构性缺陷,2 年后还会不会出同类问题)。
通俗解释(5 分钟版)
一次线上 OOM 的三层归因:
表象(T0):
用户:页面 500 了
监控:pod XXX OOMKilled,重启了
第二根因(合入/修复后的一周内):
代码里 ThreadLocal 没 remove → 线程池复用后 ThreadLocal 值累积 → OOM
→ 修了,加了 remove
第一根因(你该继续追问的):
Q: 为什么这个 ThreadLocal 没 remove 的代码能通过 code review?
Q: 为什么 Old 区到了 90% 监控没报警?
Q: 为什么单元测试没覆盖这个泄漏场景?
Q: 代码规范里有没有写"ThreadLocal 必须在 finally 里 remove"?
→ 补了监控告警、加了代码规范检查、加了集成测试
超前 2 年的系统级根因:
团队里有 5 个服务都在用 ThreadLocal,没有一个统一的 ThreadLocal 工具类封装
→ 每个服务自己管理,泄漏风险分散在各处
→ 写了一个 AutoCleanupThreadLocal 包装类,自动在 finally 里清理
→ 以后所有 ThreadLocal 都用这个包装类,这个问题永不再犯
关键细节
三层归因的判断标准
层级 问的问题 判断标准 谁来做
────────────────────────────────────────────────────────────────────────
表象 发生了什么? 用户/监控能看到的现象 值班/oncall
影响多大?
第二根因 代码哪里写错了? 能定位到代码行/配置项 开发
怎么修?
第一根因 为什么这个错误能上线? 能回答"哪个流程缺失了" 技术 Leader
哪个流程/规范/监控缺失了? 并且补上了流程/规范/监控
超前 2 年 这个系统的结构性缺陷是什么? 能抽象出一个通用方案/ 架构师/技术
2 年后还会不会出同类问题? 框架/规范,跨服务复用 负责人
让同类问题永不再犯
每层的具体追问清单
第二根因追问(修 bug 不是终点):
不只是修代码,还要问:
- 这个 bug 是什么时候引入的?(git blame 看提交历史)
- 引入了多久才发现?(1 天 vs 1 个月 → 区别很大)
- 引入的 PR 有 review 吗?review 为什么没发现?
- 这个 bug 有没有在其他服务/模块里存在?(同类模式排查)
第一根因追问(补流程):
流程缺失排查表:
□ 代码规范:这个 bug 是否违反现有规范?如果规范里没有这条,要不要加上?
□ Code Review:Review Checklist 要不要加一条检查项?
□ 单元测试:这类场景是否应该在单元测试里覆盖?能不能加一个?
□ 集成测试:是否应该有一个集成测试来验证端到端行为?
□ 监控告警:为什么监控没发现?需要补什么指标/告警?
□ 灰度发布:如果灰度发布时观察久一点,是否能发现?灰度策略要不要调整?
□ 上线检查清单:是否有上线前的检查清单?要不要加一条?
超前 2 年追问(架构层面):
结构性缺陷识别:
- 这个 bug 是孤立事件还是模式问题?(同类 bug 是否在其他服务出现过?)
- 当前系统设计是否让这类问题容易发生?怎么设计让它不可能发生?
→ 例:ThreadLocal 手动管理容易泄漏 → 统一封装自动清理
→ 例:数据库连接手动管理容易泄漏 → 连接池 + try-with-resources 规范
→ 例:Redis 过期时间手动设容易忘 → 框架层强制默认过期时间
- 能否通过框架/中间件/平台能力一劳永逸?
→ 例:排查了 10 次 OOM 后发现 6 次都是 ThreadLocal → 直接写进框架层
实战示例
案例:订单服务 P99 从 200ms 飙到 5s:
表象:
用户投诉下单慢,P99 从 200ms → 5s
监控显示订单服务 P99 突然飙高
第二根因(排查到代码行):
arthas trace 发现 OrderService.validateCoupon() 耗时 4.5s
→ validateCoupon() 里调了优惠券服务的一个新接口
→ 优惠券服务的营销同事上线了一个新的优惠规则匹配逻辑
→ 那个逻辑里有个正则回溯(catastrophic backtracking)
→ 修了正则表达式
第一根因(补流程):
Q: 为什么优惠券服务的新逻辑上线后没有压测?
→ 补了规定:所有新接口上线前必须压测,P99 < 100ms 才能发
Q: 为什么订单服务调优惠券没有超时熔断?
→ 加了 Sentinel 限流 + 熔断,优惠券服务超时 > 500ms 直接降级
Q: 为什么 P99 飙到 5s 了才有人发现?
→ 补了 P99 告警:P99 > 500ms 就报警
超前 2 年:
整个公司所有对外的 HTTP 调用都没有统一的超时/熔断/降级机制
→ 写了一个统一 RPC 框架封装(基于 Sentinel + 统一配置)
→ 所有服务的 RPC 调用都强制有超时 + 熔断,防患于未然
延伸追问
- Q:三层归因和 5 Whys 有什么区别? → 5 Whys 是”连续问 5 个为什么”,方向是线性的。三层归因是结构化的:代码错了(第二根因)→ 流程漏了(第一根因)→ 架构有缺陷(超前 2 年)。三层归因让你不会只停在修代码,而是系统性地提升。
- Q:小 bug 也需要三层归因吗?会不会太形式主义? → 不需要每个 bug 都做。判断标准:① 影响用户数 > N(你们自己定);② 同样类型的 bug 出现过至少 2 次;③ 修复花了超过 1 小时。满足任一条件就值得做三层归因。
- Q:超前 2 年的根因实际怎么落地? → 关键是不要在故障复盘会里提一嘴就忘。做法:把”超前 2 年”的改进点放进技术 Roadmap(排期、定 Owner、定验收标准),作为下个迭代的技术改进项,而不是永远在”有空的时候做”。
我的记法
- 三层 + 超前:表象(what)→ 第二根因(code bug)→ 第一根因(流程缺失)→ 超前 2 年(架构缺陷)
- 第二根因 ≠ 排障终点,第一根因才是”为什么能上线”的答案
- 超前 2 年的关键问题是:「2 年后,还会不会有其他团队踩同样的坑?」
- 一句话:「修 bug 只要 10 分钟,补流程要 1 周,改架构要 1 个月——但后两件事才是你真正防止下一次故障的保险」
状态
- 已背速记
- 能讲通俗版
- 能答追问