服务器资源占用异常问题之进程启动
背景
本人曾在服务器上部署过一个项目名为 pyteach 的前后端分离的 web 项目(此项目后端用 python 编写,且项目跟教育相关,此外用到了一些 AI 相关技术,所以起了个跟 AI 常用到的库 pytorch 相近的名字: pyteach),当时前端采用 React 技术栈,使用 npm 启动。但是为了热更新,而且本身也不是什么需要长期维护的大项目,也没有公开发布,所以就没有打包,直接使用了 npm run dev 启动,当时的启动命令是:
nohup npm run dev > run.log 2>&1 &
[3] 3398452下面返回了该进程的 PID。当时在启动这个项目的时候因为遇到了一些问题所以进行了几次重启。 最近部署一个新项目的时候发现,服务器内存、硬盘、虚拟内存全拉满了。
本人服务器配置:
- 核心:2c
- 内存:2g
- 硬盘:40g
- 带宽:3Mbps
且挂载了1g的虚拟内存。
硬盘占用高的问题与此文无关,下面只讨论内存和虚拟内存都被拉满。
问题发现
再部署一个新项目的时候发现新项目因为 OOM 被频繁 kill,于是才发现服务器内存被拉满。后面尝试找找资源占用高的进程,首先:
ps aux --sort=-%mem | head -n 15
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMANDroot 3398467 0.0 3.7 22360504 64804 pts/9 Sl 2025 32:32 node /www/wwwroot/pyteach/frontend/v3/node_modules/.bin/viteroot 3398209 0.0 0.7 22309160 13632 pts/9 Sl 2025 26:55 node /www/wwwroot/pyteach/frontend/v3/node_modules/.bin/vite上面输出部分省略了无关信息。本人诧异地发现竟然有两个不同的 PID 指向了 pyteach 这个项目。因为本人当时启动 pyteach 项目时 kill 掉了之前跟 pyteach 有关的进程。而且,上面两个 PID 跟启动项目时返回的 PID 都不一样。后面先研究为什么有两个 pyteach 进程的问题。
考虑到可能是 npm 的 dev 因为热更新,可能会开子进程监听文件变化,于是:
ps -p 3398467,3398209 -o pid,ppid,time,cmd
PID PPID TIME CMD3398209 3398192 00:26:56 node /www/wwwroot/pyteach/frontend/v3/node_modules/.bin/vite3398467 3398452 00:32:33 node /www/wwwroot/pyteach/frontend/v3/node_modules/.bin/vite根据 ppid 显示上面提到的两个进程并不是父子进程的关系。后面 lsof -i:3000 命令发现( pyteach 前端监听 3000 端口)输出的 pid 也只有 3398467,到这里怀疑 3398209 这个进程可能是个意外,但是担心随便 kill 进程会影响到项目运行,于是继续排查,打算直接看进程树:
pstree -ap | grep -A 20 -B 5 vite
`-bash,3394069 |-npm run dev,3398192 | |-node,3398209 /www/wwwroot/pyteach/frontend/v3/node_modules/.bin/vite | | |-esbuild,3398223 --service=0.25.12 --ping | | | |-{esbuild},3398224 | | | |-{esbuild},3398225 | | | |-{esbuild},3398226 | | | |-{esbuild},3398227 | | | |-{esbuild},3417650 | | | `-{esbuild},2882284 | | |-{node},3398210 | | |-{node},3398211 | | |-{node},3398212 | | |-{node},3398213 | | |-{node},3398214 | | |-{node},3398215 | | |-{node},3398216 | | |-{node},3398217 | | |-{node},3398218 | | |-{node},3398228 | | |-{node},3398229 | | |-{node},3398230 | | |-{node},3398231 | |-{npm run dev},3398205 | |-{npm run dev},3398206 | |-{npm run dev},3398207 | `-{npm run dev},3398208 `-npm run dev,3398452 |-node,3398467 /www/wwwroot/pyteach/frontend/v3/node_modules/.bin/vite | |-esbuild,3398478 --service=0.25.12 --ping | | |-{esbuild},3398479 | | |-{esbuild},3398480 | | |-{esbuild},3398481 | | |-{esbuild},3398482 | | |-{esbuild},3398520 | | `-{esbuild},3453567 | |-{node},3398468 | |-{node},3398469 | |-{node},3398470 | |-{node},3398471 | |-{node},3398472 | |-{node},3398473 | |-{node},3398474 | |-{node},3398475 | |-{node},3398476 | |-{node},3398483 | |-{node},3398484 | |-{node},3398485 | |-{node},3398486(输出省略了无关部分)可以发现在 PID 为 3394069 这个 bash 中,有两个不同的 npm 命令在运行,其中 npm run dev 后的两个 PID 分别对应前面 ps -p 命令中看到的两个不同进程的 ppid (即父进程 id)。此时可以确凿 3398209 这个 node 进程真的就是意外。后面好奇自己为什么没发现这个进程,因为 pyteach 监听 3000 端口,本人启动前 kill 干净了一切监听 3000 端口的进程。后面思考到,相同的项目被重复启动时,监听的端口号会自动 +1,于是:
lsof -i:3001
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAMEnode 3398209 root 26u IPv4 481200389 0t0 TCP *:origo-native (LISTEN)发现还真有 node 3398209 (旧 node 进程)这个进程在监听端口,而且监听的是一个不用的 3001 端口。可能是本人正式启动 pyteach 前,之前的测试进程没 kill 干净。于是接下来直接 kill 跟 node 3398209 进程的父进程 npm 3398192。
kill 3398192pstree -ap | grep -A 20 -B 5 vite
`-bash,3394069 |-npm run dev,3398192 | |-node,3398209 /www/wwwroot/pyteach/frontend/v3/node_modules/.bin/vite | | |-esbuild,3398223 --service=0.25.12 --ping | | | |-{esbuild},3398224 | | | |-{esbuild},3398225 | | | |-{esbuild},3398226 | | | |-{esbuild},3398227 | | | |-{esbuild},3417650 | | | `-{esbuild},2882284 | | |-{node},3398210 | | |-{node},3398211 | | |-{node},3398212 | | |-{node},3398213 | | |-{node},3398214 | | |-{node},3398215 | | |-{node},3398216 | | |-{node},3398217 | | |-{node},3398218 | | |-{node},3398228 | | |-{node},3398229 | | |-{node},3398230 | | |-{node},3398231 | |-{npm run dev},3398205 | |-{npm run dev},3398206 | |-{npm run dev},3398207 | `-{npm run dev},3398208 `-npm run dev,3398452 |-node,3398467 /www/wwwroot/pyteach/frontend/v3/node_modules/.bin/vite | |-esbuild,3398478 --service=0.25.12 --ping | | |-{esbuild},3398479 | | |-{esbuild},3398480 | | |-{esbuild},3398481 | | |-{esbuild},3398482 | | |-{esbuild},3398520 | | `-{esbuild},3453567 | |-{node},3398468 | |-{node},3398469 | |-{node},3398470 | |-{node},3398471 | |-{node},3398472 | |-{node},3398473 | |-{node},3398474 | |-{node},3398475 | |-{node},3398476 | |-{node},3398483 | |-{node},3398484 | |-{node},3398485 | |-{node},3398486
lsof -i:3001
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAMEnode 3398209 root 26u IPv4 481200389 0t0 TCP *:origo-native (LISTEN)
ps -p 3398192,3398209 -o pid,ppid,stat,cmd
PID PPID STAT CMD3398192 3394069 Sl npm run dev3398209 3398192 Sl node /www/wwwroot/pyteach/frontend/v3/node_modules/.bin/vitekill 以后可以发现这个进程竟然还活着,而且 3001 端口也还在被监听。
后面查询到 kill 命令只是给进程发一个 SIGTERM 信号,告诉进程去正常退出,比如这里 kill 一个 npm 3398192 进程的时候,npm 进程虽然收到了信号,但是可能自己拉起的 node 进程即一系列子进程还在运行,所以忽略了 kill 信号。
后面尝试 kill 核心进程 node 3398209,才真正把 3398209 这个没用的 node 进程的进程链 kill 掉,看来擒贼先擒王也不是一直有用,找到关键点才是最重要的。
总结
这里也学到了一个小知识,在 bash 执行一个命令时(例如 npm 命令),会创建 npm 子进程,而 npm 子进程又会拉起自己的 node 进程,所以执行一个命令可能启动了很多很多子进程,以后想详细排查想 pyteach 项目这样的 web 进程状态,光靠 lsof 来看监听端口确实有点草率了,以后这个习惯得改。
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!