记录一次简单的线上故障分析(20231116)

今天是「真白酱机器人」开放使用的第一天。晚上九点,真白酱的介绍视频定时发布,我把真白酱拉到几个群里,试用了一下,能够正常运行,之后就去上厕所了。


晚上 10 点 56 分,有小伙伴发现真白酱已经无法使用。

3 个赞

开始

0 点 10 分,我从厕所出来,发现真白酱已经无法正常使用。

真白酱上线前已经做了非常充足的审计准备,因此我不太担心由于准备不充分导致的问题,接下来按照流程进行排查就可以了。

3 个赞

一、使用 htop 进行进程检查

第一步,登录 SSH,然后直接输入 htop。htop 可以检查整个系统和各个进程的资源占用情况。

通常情况下,按照内存(RES)排序可以将主要进程置于最前端,比按 CPU 排序的优点在于内存变动一般不频繁,进程不会经常换位。

Koishi 的 Node 进程位居第二位,仅次于 MySQL。查看内存发现内存占用为 394M,虽然超过了基准占用 120M,但仍然处于正常水平。而 CPU 占用始终为 100%,这表明一个核心完全被 JS 占用,基本可以确定是某处代码死循环的问题了。接下来就需要确定是哪里的代码导致了死循环。

2 个赞

二、开启调试器

Node 具有打开正在运行中进程的调试器的功能,在 Node 的官网上 就有介绍

要开启调试器,首先要知道进程的 PID。在 htop 的最左侧可以直接找到进程的 PID:3837190

接下来,最简单的方法,给进程发送 SIGUSR1 信号即可。在这里我比较习惯使用另一种很少人知道的方法:新开一个 Node,输入下面的代码回车即可。

这招在干某个大型国产 Electron 应用的时候很好用(

process._debugProcess(3837190)

回车后函数返回 undefined,表明运行成功。连按两次 Ctrl-C 退出 Node。

接下来,需要确保调试器已经开启。默认开启的端口是 9229。输入 netstat -ntpl 并回车,看到有 0.0.0.0:9229 字样就是成功开启调试器了。

3 个赞

三、使用 F12 调试

在服务器上开启调试器后,接下来使用任意一个端口转发工具(网上似乎常叫「穿透工具」)将服务器的 9229 端口转发到本地即可。为了方便,我直接使用了终端自带的端口转发工具。

启动端口转发以后,直接打开浏览器,输入 chrome://inspect。等待几秒钟,页面上就会自动出现我们想要调试的进程名称和 PID 了。点击下方的 inspect 按钮打开 F12。

5 个赞

四、截取内存快照,录制 CPU 火焰图

通过上面的 htop 分析,我们已经确定问题处在 CPU 而不是在内存上,但留存现场的时候一般还是要尽可能保存多的内容。

首先打开 Memory 选项卡,截取内存快照。直接点击页面下方的「Take Snapshot」,F12 就会自动开始截取快照了。

接下来打开 Performance 选项卡,点击录制按钮,开始录制 CPU 火焰图。录制时间的长短取决于 htop 分析的结果;如果 CPU 是持续占用,那么录制 3-5 秒就足够;而如果 CPU 是隔几秒满载一下,那么就需要录制更长的时间,确保满载情况被录制 3 次以上即可。

imag6

录制结束后点击上方的「保存」按钮保存在本地。返回 Memory 选项卡,对刚刚打好的内存快照右键「Save」即可。

接下来,赶紧 pm2 restart 0 重启进程,恢复线上的运行。在群内测试真白酱已经恢复功能后,就可以安心检查我们刚刚截取的快照了。

3 个赞

五、火焰图分析

由于内存上没有问题,我们就不分析内存快照了。

仍然打开 chrome://inspect,这次我们直接选择 Open dedicated DevTools for Node,打开一个空的 F12。

选择 Performance,点击上方的「加载」图标,加载之前录制好的火焰图。

选择「Bottom-Up」选项卡,点击「展开右侧栏」按钮,我们可以直接看到是 showAnalysis 这个函数占用了 100% 的 CPU 时间,也就可以判断是这个函数内出现了死循环。此外,我们还可以直接看到是 num-theory-analyzer 这个插件造成了这个问题。

有了这么多信息,我们就可以直接去阅读源码了。

2 个赞

六、看看源码

直接在 npm 上 打开这个插件的源代码,找到 showAnalysis 函数:

showAnalysis(): string[] {
  // ...
  let factors = this.getFactors();
  // ...
  let numbers = this.findSquaresSumNumbers();
  // ...
  let sumOfSeqResult = this.isSumOfSequence();
  // ...
  return output;
}

可以看到里面调用了很多其他的函数。来看看其中的 getFactorsisSumOfSquares

getFactors(): number[] {
  let factors: number[] = [];
  for (let i = 1; i <= this.input; i++) {
    // ...
  }
  return factors;
}

isSumOfSquares(): boolean {
  for (let a = 0; a * a <= this.input; a++) {
    // ...
  }
  return false;
}

不难看出两个函数在输入数据过大的时候的计算时间将会成倍增加,这就是 CPU 跑满的原因了。所以问题其实不是死循环,最终 CPU 可以算完的((

接下来,就要请作者处理这个问题了。最简单的方式,在本论坛 at 作者并请求修改插件即可:

3 个赞

@araea 能不能给 num-theory-analyzer 插件加个数字上下限的限制呢?

2 个赞

好滴,没问题 (`・ω・´)ゞ 今晚就加一个可以自定义上限和下限的配置项喵 (。・ω・。)ノ:heart:

还有,il 你好认真好专业!!!(✪▽✪) 爱了喵 (/∇\)

好啦喵 ~ 更新完毕 ~

2 个赞

thread_28501554_20191225102412_s_907755_w_340_h_323_18548

2 个赞

一定是上厕所导致的(智将)

3 个赞

道理我都懂,但是美少女才不会上厕所

6 个赞

这个群小可爱是谁呀
好想rua

4 个赞

这个群小可爱是谁呀
好想rua

2 个赞

这个群小可爱是谁呀
好想rua

2 个赞