它没卡死,只是慢:一次深夜排障里三个反直觉的坑
它没卡死,只是慢:一次深夜排障里三个反直觉的坑
本文分享技术排查过程与纠错方法,不含商业推广。
凌晨一点多,我自托管的助手连不上了。
进程在,内存占着,端口 18789 没人监听。日志停在两行之间:已经打完 shutdown budget at startup,还没走到 [plugins] loading。
第一反应是”卡死了”。接下来几个小时证明:这个判断本身就是最大的坑。
坑一:把”慢”读成”死”——因为量错了进程
“卡死”总得有个证据。我抓到的证据看起来很硬:CPU 0.1%,日志 18–30 分钟不推进。
于是整个维修方向都建立在”它冻住了”上面。直到换了正确的采样对象再量一次:
| 采样对象 | CPU | 结论 |
|---|---|---|
| 我一开始量的那个进程 | 0.1% | “冻住了” |
| 真正在启动的网关主进程 | 58% 核在忙 | 它一直在干活,只是慢 |
前一个是什么?是数据库只读快照的辅助进程——它本来就该闲着。在启动链路里,它既不是瓶颈也不是信号。
同一个角色,两次表现完全不同(0.1% vs 58%),这本身就是结论:
该下的是”这条路径不可靠“,不是”它固定卡死在某处“。
——前者要求你换路径并量化,后者只会让你守着它等。
可迁移的动作:报”卡死”之前,先回答一句——我量的这个进程,是这条链路里的真身吗? 名字像、父子像,都不算;命令行和它在链路里的角色才算。
坑二:”急救”把病情加重了
进程不监听端口,最自然的动作是再拉一次。我们的看门狗就是这么干的:探活失败 → 重新触发计划任务。
它踩了两个坑,都写进了日志:
- 计划任务设了
MultipleInstancesPolicy=IgnoreNew,重复启动会被直接拒绝,返回0x800710E0。这个码常被读成”端口被占”,其实是**”重复启动被拒”**。 - 就算真起了第二个网关,它会因为**”已有进程占用 state 目录”**而退出——并且把正在跑的启动时钟清零。
结果:启动迁移本来要跑 7–8 分钟,看门狗每 5 分钟催一次,每次都在它快跑完时把它拽回起点。
当”启动”本身是一个分钟级的慢过程时,探活阈值和重启策略必须一起设计。
阈值比启动时间短,监控就从”救火”变成了”纵火”。
修法(后来落进脚本):把最大等待提到 900 秒;检测到已有网关进程在启动就跳过启动、只进入等待循环;连续失败 3 次就暂停,别无限重试。
一句话判据:启动迁移跑着的时候,别重启它。
坑三:把”改了”当成”修好了”
“隐藏控制台缓冲区写满、把写日志的进程堵住”——这个假说听起来很合理。于是给启动脚本打了个补丁:换到真实工作目录,把输出重定向到文件而不是隐藏控制台。
补丁上线,脚本”跑起来了”,看起来好了一点。
但我们没有直接宣布修好,而是做了交换测试:杀掉当前实例,让计划任务用新脚本重新拉。
| 启动路径 | 结果 |
|---|---|
| 服务路径(计划任务 → 脚本)打补丁后 | 06:24:56 起,到 06:34:50 仍未绑端口 |
| 服务路径(补丁前) | 同样停在两行之间 |
| pty 路径(直接起进程) | 两次都顺利绑上端口,健康检查 200 |
同一台机器、同一个版本、同一份数据库——换一条启动路径,结果就不同。
所以结论只能写成:补丁没有解决问题,假说被证伪。补丁留着(换工作目录 + 落盘日志本身无害,还方便取证),但不能算作修复。
“改了”和”修好了”之间,隔着一次交换测试。
没有对照的”好像好点了”,在深夜四点最容易被当成结论。
收束:两条可以搬走的判据
- 同一角色、两次不同表现 ⇒ 说”不可靠”,不说”固定故障”。
“固定卡死”会把你钉在等待上;”不可靠”才会逼你换路径、做对照、量化差异。 - 数字必须带对象和时点。
这一夜里,我们差点引用两个”铁证”数字——一个计数、一个日志行号。追问下去:计数是某次实测、某个时刻、某个数据库的读数,库后来被迁移过;行号只是一次检索输出的前缀,源文件早已轮转。同一个数字,在两次采样里可能是两回事。
最后那句”是不是卡死”,答案往往是:它没卡死,只是慢。 而”慢”这件事,只有量对对象、给够时间、做完对照,才看得清。