今天是「真白酱机器人」开放使用的第一天。晚上九点,真白酱的介绍视频定时发布,我把真白酱拉到几个群里,试用了一下,能够正常运行,之后就去上厕所了。
晚上 10 点 56 分,有小伙伴发现真白酱已经无法使用。
今天是「真白酱机器人」开放使用的第一天。晚上九点,真白酱的介绍视频定时发布,我把真白酱拉到几个群里,试用了一下,能够正常运行,之后就去上厕所了。
晚上 10 点 56 分,有小伙伴发现真白酱已经无法使用。
第一步,登录 SSH,然后直接输入 htop
。htop 可以检查整个系统和各个进程的资源占用情况。
通常情况下,按照内存(RES)排序可以将主要进程置于最前端,比按 CPU 排序的优点在于内存变动一般不频繁,进程不会经常换位。
Koishi 的 Node 进程位居第二位,仅次于 MySQL。查看内存发现内存占用为 394M
,虽然超过了基准占用 120M
,但仍然处于正常水平。而 CPU 占用始终为 100%
,这表明一个核心完全被 JS 占用,基本可以确定是某处代码死循环的问题了。接下来就需要确定是哪里的代码导致了死循环。
Node 具有打开正在运行中进程的调试器的功能,在 Node 的官网上 就有介绍。
要开启调试器,首先要知道进程的 PID。在 htop 的最左侧可以直接找到进程的 PID:3837190
。
接下来,最简单的方法,给进程发送 SIGUSR1
信号即可。在这里我比较习惯使用另一种很少人知道的方法:新开一个 Node,输入下面的代码回车即可。
这招在干某个大型国产 Electron 应用的时候很好用(
process._debugProcess(3837190)
回车后函数返回 undefined
,表明运行成功。连按两次 Ctrl-C
退出 Node。
接下来,需要确保调试器已经开启。默认开启的端口是 9229
。输入 netstat -ntpl
并回车,看到有 0.0.0.0:9229
字样就是成功开启调试器了。
在服务器上开启调试器后,接下来使用任意一个端口转发工具(网上似乎常叫「穿透工具」)将服务器的 9229
端口转发到本地即可。为了方便,我直接使用了终端自带的端口转发工具。
启动端口转发以后,直接打开浏览器,输入 chrome://inspect
。等待几秒钟,页面上就会自动出现我们想要调试的进程名称和 PID 了。点击下方的 inspect
按钮打开 F12。
通过上面的 htop 分析,我们已经确定问题处在 CPU 而不是在内存上,但留存现场的时候一般还是要尽可能保存多的内容。
首先打开 Memory 选项卡,截取内存快照。直接点击页面下方的「Take Snapshot」,F12 就会自动开始截取快照了。
接下来打开 Performance 选项卡,点击录制按钮,开始录制 CPU 火焰图。录制时间的长短取决于 htop 分析的结果;如果 CPU 是持续占用,那么录制 3-5 秒就足够;而如果 CPU 是隔几秒满载一下,那么就需要录制更长的时间,确保满载情况被录制 3 次以上即可。
录制结束后点击上方的「保存」按钮保存在本地。返回 Memory 选项卡,对刚刚打好的内存快照右键「Save」即可。
接下来,赶紧 pm2 restart 0
重启进程,恢复线上的运行。在群内测试真白酱已经恢复功能后,就可以安心检查我们刚刚截取的快照了。
由于内存上没有问题,我们就不分析内存快照了。
仍然打开 chrome://inspect
,这次我们直接选择 Open dedicated DevTools for Node
,打开一个空的 F12。
选择 Performance,点击上方的「加载」图标,加载之前录制好的火焰图。
选择「Bottom-Up」选项卡,点击「展开右侧栏」按钮,我们可以直接看到是 showAnalysis
这个函数占用了 100% 的 CPU 时间,也就可以判断是这个函数内出现了死循环。此外,我们还可以直接看到是 num-theory-analyzer
这个插件造成了这个问题。
有了这么多信息,我们就可以直接去阅读源码了。
直接在 npm 上 打开这个插件的源代码,找到 showAnalysis
函数:
showAnalysis(): string[] {
// ...
let factors = this.getFactors();
// ...
let numbers = this.findSquaresSumNumbers();
// ...
let sumOfSeqResult = this.isSumOfSequence();
// ...
return output;
}
可以看到里面调用了很多其他的函数。来看看其中的 getFactors
和 isSumOfSquares
:
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 作者并请求修改插件即可:
好滴,没问题 (`・ω・´)ゞ 今晚就加一个可以自定义上限和下限的配置项喵 (。・ω・。)ノ
还有,il 你好认真好专业!!!(✪▽✪) 爱了喵 (/∇\)
好啦喵 ~ 更新完毕 ~
一定是上厕所导致的(智将)
道理我都懂,但是美少女才不会上厕所
这个群小可爱是谁呀
好想rua
这个群小可爱是谁呀
好想rua
这个群小可爱是谁呀
好想rua