经验与方法论 · 共识经验 · 收录于 2026-09-01

诡异 Bug 查不出来?不是运气差——把排障从「猜」变成「证据链」

症状

先讲一个我至今记得的下午。同事跑过来:「Google 能打开,但那个视频站打不开了,是不是它挂了?」我第一反应也是「那网站挂了呗」。但多问了一句:同一台电脑、同一个浏览器、同一时间,一个域名通一个不通——如果是网站挂了,为什么不是全站都挂?这个「为什么」就是排障的起点:绝大多数「诡异 bug」,不是问题真的诡异,是我们把「表象」当成了「事实」。

类似的场景你一定见过:服务重启一下就好了,但过几天又挂,你怀疑是「偶发」;接口偶尔超时,你怀疑是「网络波动」;只有一台机器限流,你怀疑是「流量变大了」。然后你开始猜:改配置、重启、加超时、调参数……运气好蒙对了,运气不好折腾一晚上,第二天它自己又好了——于是你更坚信它是「玄学」。

我见过最典型的反面教材:一个考务系统,报名高峰时网站打不开,运维重启就恢复,跑几天又打不开。反复重启了快一个月,直到有人发现故障时间点都发生在有人上传 ZIP 附件的时候——提取样本一看,是恶意构造的 ZIP 炸弹:文件在内存里解压,直接吃爆 Java 堆。如果早一个月把「故障时间」和「用户操作」关联起来,这个事故根本不用扛一个月。

还有一次异地多活切换,限流告警响了。第一反应都是「流量变大了」——但把异常时段和正常时段的请求曲线一对比,19:16 的请求量只有 60 多,还不如平时高峰的零头;再看,限流只出现在一台机器上。流量没变大、其他机器都正常——限流是果,不是因。往单机里挖,才发现是 GC 停顿把请求处理卡死了。如果当时顺着「流量变大」的直觉去扩容,扩了也白扩。

如果你也经历过「重启大法」「改配置玄学」「怀疑人生一整晚」——这篇文章是写给同类人的。它不是某个 bug 的教程,是把多次独立事故里反复验证过的排障方法,提炼成一条可复制的证据链:先分层、再取证、找变量、对基线、拿样本。用这套方法,诡异 bug 的「诡异」会消失大半。

原因

先想清楚:为什么我们遇到 bug 第一反应是「猜」,而不是「查」?

第一,人脑的归因机制是「就近联想」,不是「系统分析」。 网站打不开,最近的联想是「网站挂了」;接口超时,最近的联想是「网络不好」;重启好了,最近的联想是「是偶发」。这不是笨,是大脑默认走捷径。

而系统故障恰恰是分层的:一个请求从 DNS 解析到 TCP 连接、TLS 握手、HTTP 请求、应用处理、数据库查询,任何一层出错,表象都是「打不开」「超时」「报错」——表象相同,层不同,修法完全不同。猜,就是在七层里随机挑一层赌。

第二,我们把「故障」和「根因」混为一谈。 限流告警是故障,但限流往往是果不是因——真正的原因可能在 GC、线程池、连接池、慢查询。看到告警就处理告警,等于看到发烧就只吃退烧药,炎症还在。排障的难点从来不是「修」,是「把果剥到因」。

第三,我们急着「修好」,不愿意「取证」。 重启是取证的天敌:重启把现场销毁了。很多诡异 bug 之所以诡异,是因为每次都被重启/重试「治好」了,永远没机会留下证据。排障和破案一样,现场没了,案子就没了。

下面每条经验,都是把排障从「猜」拉回「证据链」的做法——它们不是一家之言,是几套独立事故处理反复验证过的共识。

解法

下面每条经验都按「现象 → 做法 → 为什么」展开。它们不是一家之言,而是多篇独立实践里反复出现的共识。

经验 0(L1 一句话结论):排障五步——分层、取证、找变量、对基线、拿样本

遇到诡异 bug,按顺序走:①把表象拆成链路,逐层排除(DNS/TCP/TLS/HTTP/应用/数据库);②每个结论都要有可复现的测试支撑,用词严谨到可以被推翻;③找「变量」和「触发点」——故障一定是某个变化引发的,重启恢复又复发 = 触发式故障;④拿异常时段和正常基线对比,把「想当然」排除掉;⑤先复现、拿样本,再动手修。 赶时间只看这一条。下面经验 1~5 是为什么和怎么做。

经验 1:先分层,把「表象」拆成「链路」

现象:一个网站打不开,多数人的第一反应是「网站挂了」。但「某个域名打不开」只是表象——故障可能发生在 DNS 解析、TCP 连接、TLS 握手、HTTP 请求、CDN、代理、甚至浏览器扩展的任何一层。

我在一次真实排查里见过完整的链条:Google 能打开、某个视频站打不开,逐层测下来——DNS 解析用的是 nslookup 发现解析超时,用 curl --resolve 绕过 DNS 直连 CDN 后 HTTP 200——故障被精确定位到「当前网络的 DNS 解析链路」,而不是网站本身。

做法:①先画链路:客户端 → DNS → TCP → TLS → HTTP → 应用 → 数据库,每一层都有对应的验证命令;

②用工具逐层打点:nslookup/dig 查解析,curl -v 看卡在哪个阶段(Connected to 之前是 DNS/TCP,SSL connected 之后才是 TLS/HTTP),curl --resolve 绕过某层验证下一层,ping/traceroute 看路由;

③「某层正常」和「某层出错」都要记录,排除法做完,剩下的那层就是嫌疑层。

为什么:系统是分层的,表象不是事实——「打不开」只是最外层的信息,真正的故障层级决定了完全不同的修法(改 DNS 配置 vs 修证书 vs 调应用)。逐层打点不是慢,是把「猜七层」变成「查一层」。

经验 2:证据链思维——结论可以窄,但必须实

现象:DNS 排查到最后,结论是「当前网络的 DNS 解析链路异常」。当时有人问:「那就是运营商屏蔽了呗?」——不。客户端测试只能证明故障发生在 DNS 解析这一层,至于路由器内部是缓存异常、上游 DNS 故障、还是过滤规则,客户端证据无法唯一确定。把结论说窄,不是保守,是诚实:窄结论可以被下一步证据推翻或坐实,宽结论(「运营商屏蔽」)一旦说出口,排查就停了。

做法:①每个结论都要附带「我怎么测的、复现命令是什么」;②用词分级:「异常」≠「屏蔽」,「疑似」≠「实锤」,「当前网络」≠「全局」;③结论写出来先自我检查:这句话能被什么证据推翻?如果什么都推翻不了,它就不是结论,是猜测。

为什么:排障最大的风险不是「不知道」,是「知道错了」——一个听起来合理的错误结论会终止排查(「运营商屏蔽,没办法」),而一个严谨的窄结论会引导下一步取证。证据链的终点是「可复现」,不是「听起来对」。

经验 3:找「变量」和「触发点」,别信「偶发」

现象:考务系统反复打不开、重启就恢复——「偶发」这个词害死人。直到有人把故障时间点和操作关联起来:每次故障都发生在有人上传 ZIP 附件之后。顺着这个变量提取样本,发现是恶意 ZIP(ZIP 炸弹:压缩包极小,解压后是巨量重复数据,在内存里直接解爆 Java 堆)。一个月的问题,半小时定位。

做法:①故障发生时立刻记三件事:时间、最近一次变更(发布/配置/操作)、故障范围(单机/全局限定范围);②「重启恢复」不是结论,是线索——重启恢复又复发 = 触发式故障,一定有个触发条件没被找到;③把「故障时间线」和「操作时间线」并排画,找重叠点;④怀疑「偶发」前,先问自己:有没有连续记录过三次以上的故障上下文?没有记录,就没有资格说「偶发」。

为什么:计算机系统里没有「偶发」——同一输入同一状态必然同一输出。所谓偶发,只是触发条件还没找到。而触发条件几乎总是「变量」:一次发布、一个操作、一个特殊输入。找变量比找根因快,因为变量是可见的、可比的。

经验 4:对比基线,限定范围——把「想当然」排除掉

现象:异地多活切换后限流告警,第一反应是「流量变大了」。把异常时段的请求曲线和正常基线一对比:19:16 请求量 60 多,平时高峰 400 多——流量根本没变大;再看,限流只出现在一台机器上。两个对比做完,「流量变大」被排除,问题锁定单机,往里挖才发现是 GC 停顿。如果跟着直觉去扩容,扩了也白扩。

做法:①任何「因为 A 所以 B」的归因,先找对照:异常时段 vs 正常时段、故障机 vs 健康机、改前 vs 改后;②数据说话:把曲线、日志、指标拉出来对比,别凭感觉;③限定范围:单机问题 vs 全局问题、单用户 vs 全用户、单接口 vs 全接口——范围越小,变量越少;④「排除法」的每个排除都要有证据,排除完剩下的就是答案,不用猜。

为什么:归因的常见死法是「想当然」——流量大、网络差、机器烂,都是听起来合理的解释。对比基线是成本最低的证伪手段:一个曲线对比就能排除「流量变大」,一个单机对比就能排除「全局故障」。限定范围是把「大海捞针」变成「碗里捞针」。

经验 5:先复现、拿样本、再动手修

现象:考务系统事故里,如果只是「关闭上传接口」就完事,ZIP 炸弹的根因(内存解压 + 无大小限制)永远不会被发现,换个入口(比如表单导入)还会炸。真正的处理是:提取样本 → 分析构造(重复字符 + 极小压缩比 = 炸弹特征)→ 修代码(解压到磁盘 + 限制大小)→ 验证。

再比如一次 yfinance 调试:Chart API 正常、历史数据超时,看起来是「网络问题」——实际进源码看,是 SOCKS5 代理下某个请求路径没有走代理逻辑。

不拿样本、不看源码,永远到不了这一步。

做法:①修复前先问:这个 bug 我能复现吗?不能复现的修复是盲修;②尽量留下现场:日志、堆栈、请求样本、复现脚本——重启前先 dump;③拿到的样本要「解剖」:不只看报错表面,看数据的构造(大小、内容、结构),样本会说真话;④修完必须验证「同类输入不再触发」,不只是「这个输入不报错」。

为什么:复现是根因分析的唯一可靠前提——不能复现,就无法验证修复是否真的有效,只能「赌」。样本是现场的唯一证据,解剖样本才能从「修表象」到「修根因」。排障的最后一步不是「修好了」,是「我知道了为什么,并且同类不会再犯」。

怎么迁移到你的场景

这套方法不挑技术栈,挑的是习惯。落地三步:

  1. 建一个「排障模板」(笔记软件里存一份):分层链路清单(你的系统是哪些层)、常用取证命令、时间/变更/范围三要素记录表。下次故障直接照着填,不用现场想。
  2. 把「重启大法」升级成「重启前取证」:重启前至少留三样东西——报错原文、最近变更、故障时间。哪怕这次没时间查,样本留着,下次复现时它就是破案关键。
  3. 每周花 10 分钟复盘一次「本周 bug」:不用写报告,就问三个问题——我当时第一反应是什么?我有没有先取证再动手?如果重来一次,哪一步能更快?三个月后你会明显感觉到,「诡异」变少了。

最后一句话:排障能力不是天赋,是取证纪律。每次忍住「先猜一把」的冲动、多跑一条验证命令,都是在给下一次事故攒证据。

收尾:写这篇之前,我刚又犯过一次「想当然」——线上问题查了两个小时,最后发现是缓存没失效,而我的第一反应是「代码肯定没问题」。排障最贵的从来不是工具,是「我觉得」这三个字。把五个步骤贴在工位上:分层、取证、找变量、对基线、拿样本。下次再遇到诡异 bug,先走完流程再下结论——你会少熬很多个夜。

不绑定工具

本条目为共识型经验,不挂接具体工具;相关工具类问题请到对应工具的问题页。

来源与证据

收录 共识经验 · 不承诺实测
本条目为共识型经验:不承诺本地实测,案例已脱敏提炼进正文,外部来源不逐一列出。

← 经验与方法论 问题域 | 问题货架 | 遇到其它问题?去 问题空间 提交求助卡