ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Node.js后台进程更新后如何正确重启:nohup、pm2与forever实操指南

Node.js后台进程更新后如何正确重启:nohup、pm2与forever实操指南

1. 项目背景:搞清楚“更新文件”和“后台运行”之间的那点事

先说个最容易被新手忽略的坑:很多人以为把wework-verify.js文件改完保存,后台的服务就“自动生效”了。实测下来根本不是这么回事。Node.js 进程启动之后,代码是加载到内存里跑的,你改的是磁盘上的文件,跟已经跑起来的进程完全是两码事。除非你用了nodemon这类带自动重启的监听工具,否则改完文件必须手动重启进程,新代码才会真正生效。

这篇文章解决的就是这个场景:一台服务器(或者你自己的电脑)上,wework-verify.js正以某种方式在后台运行,你更新了文件内容,现在需要一条命令让它重新加载新代码继续跑。

我的建议是先把“后台运行”的本质弄清楚。日常操作里,所谓“后台运行”无非是三种情况:

  1. 用nohup+&启动的普通进程,靠 PID 管理。
  2. 用pm2这个 Node.js 进程守护工具管理的服务。
  3. 用forever或者其他类似工具托管的进程。

这三种方式的更新命令完全不一样,用错了轻则重启失败,重则直接杀掉进程还得重新找启动参数。后面我会挨个说清楚,你只要对照自己当初是怎么启动的,抄对应的命令就行。

2. 先判断你属于哪种运行方式

2.1 用 nohup 启动的裸进程

如果你当初的启动命令长这样:

nohup node wework-verify.js > app.log 2>&1 &

那说明你的服务是一个“无人看管”的裸进程。这种方式的好处是简单,缺点是进程一旦崩了不会自动拉起,更新后也要手动处理。

判断方法很简单:

ps -ef | grep wework-verify

如果能看到一个node wework-verify.js的进程,而且它的父进程 PID 是 1(或者是一个很靠前的数字),多半就是 nohup 拉起来的。

2.2 用 pm2 管理的托管进程

pm2 是目前 Node.js 项目里最主流的进程守护工具。如果你当初用类似下面的命令启动:

pm2 start wework-verify.js --name wework-verify

或者:

pm2 start wework-verify.js -i 1 --name wework-verify

那你的服务就在 pm2 的管辖范围内。pm2 的价值不只是“后台运行”,它还自带崩溃自动重启、日志管理、开机自启这些功能,所以对长期跑的服务来说,pm2 是远比 nohup 靠谱的选择。

2.3 用 forever 管理的进程

forever 也是老牌的 Node.js 守护工具,虽然现在用的人少了,但不少老项目里还在用。启动命令一般长这样:

forever start wework-verify.js

判断方式是执行:

forever list

如果列表里有wework-verify.js,那就是 forever 管的。

3. 各方式对应的更新命令,直接照抄

3.1 如果你是 nohup 方式

核心步骤是两步:杀掉旧进程 → 重新拉起新进程。

先找到 PID:

ps -ef | grep wework-verify | grep -v grep

输出会类似这样:

root 12345 1 0 10:30 ? 00:00:10 node wework-verify.js

第二列的数字12345就是 PID。杀掉它:

kill 12345

这里有个小技巧:如果直接kill杀不掉(进程可能正在处理某些请求或者处于僵死状态),再用kill -9 12345强制杀。但不要一上来就kill -9,因为强杀可能让你丢失某些未写完的数据,尤其是涉及文件读写或者数据库操作的脚本。

杀完之后,重新启动:

nohup node wework-verify.js > app.log 2>&1 &

这里再说深一点:> app.log 2>&1是把标准输出和标准错误都重定向到app.log,这样就算程序里有什么console.log或者报错信息,都能在日志文件里看到,排查问题的时候会方便很多。如果你不想输出到日志文件,只想让它在后台安静地跑,也可以写成:

nohup node wework-verify.js > /dev/null 2>&1 &

但我个人强烈建议保留日志,因为后面真出问题的时候,没有日志就只能干瞪眼。

3.2 如果你是 pm2 方式

pm2 就优雅多了,一条命令搞定:

pm2 restart wework-verify

注意这里的wework-verify不是文件名,是你在pm2 start时指定的应用名(--name后面的值)。如果你当时没指定名字,默认会变成文件名去掉后缀,那通常就是wework-verify。不确定的话先看一下:

pm2 list

输出里Name列会显示所有应用的名字。找到之后直接 restart 即可。

还有个更省事的写法,直接写文件路径:

pm2 restart wework-verify.js

pm2 也支持。它会自动匹配名字或者路径,实测下来比按名字匹配更不容易出错。

如果只是想重新加载代码而不中断进程(针对某些 HTTP 服务场景),pm2 还支持:

pm2 reload wework-verify

reload和restart的区别是:reload是平滑重载,先起一个新的工作进程,等新进程就绪后再把旧进程换掉,整个过程对客户端来说几乎无感知;restart是直接干掉旧进程再起新的,中间会有一个短暂的不可用窗口。如果你的wework-verify.js处理的是内部验证逻辑,不是对外的 HTTP 服务,用restart就完全够了;但如果它对外提供服务,reload是更好的选择。

3.3 如果你是 forever 方式

先看进程列表:

forever list

找到对应的脚本路径和 uid,然后:

forever restart wework-verify.js

或者用 uid:

forever restart <uid>

forever 没有 pm2 那么完善的平滑重载机制,但 restart 本身已经足够应对绝大多数场景。

4. 核心实操环节:整理一套完整的更新流程

4.1 更新前,先备份

这一步很多人会跳过,但我建议养成习惯。特别是你改的是wework-verify.js这种带有业务逻辑的脚本,万一改出来的代码有语法错误,可能连启动都启动不了。

备份命令很简单:

cp wework-verify.js wework-verify.js.bak.$(date +%Y%m%d%H%M%S)

后面加个时间戳,方便回溯是哪一版。

4.2 更新代码文件

这个环节没有太多可讲的,用编辑器改完保存。但有一点关键提醒:改完之后一定要检查语法。

node --check wework-verify.js

--check是 Node.js 自带的语法检查模式,只校验语法不执行代码。如果这里报错,比如多了个括号、少了个引号、某个变量没定义导致的低级错误,趁早修掉。这个命令我每次更新完文件都会跑一遍,看着它输出干净才敢去重启服务。

4.3 执行重启命令

根据你之前判断的运行方式执行对应命令,pm2 用户执行:

pm2 restart wework-verify

nohup 用户执行:

kill <PID> nohup node wework-verify.js > app.log 2>&1 &

forever 用户执行:

forever restart wework-verify.js

4.4 验证服务恢复正常

重启之后千万别急着关终端走人,得先确认服务真的起来了。这一步是区分“老手”和“新手”的分水岭。

先看进程是否存在:

ps -ef | grep wework-verify | grep -v grep

再看日志里有没有异常输出:

tail -50 app.log

如果用的是 pm2,看状态更直观:

pm2 status

status列应该显示online,如果显示errored或者stopped,说明启动时挂了。

最后是实际的业务验证。如果这个脚本是处理某种验证逻辑的,最好实际调一次接口或者触发一次流程,确认新逻辑生效。这一步一定不能省,因为有些代码逻辑错误不会导致进程崩溃,但会静默地产生错误结果。

5. 常见问题与排查技巧实录

5.1 重启之后进程立刻消失

这是最高频的问题。大部分原因是代码中有语法错误或者运行时的未捕获异常。先别急着重启,去看日志文件:

tail -100 app.log

如果是 pm2 管理,直接:

pm2 logs wework-verify --lines 100

日志里会明确写出报错原因。我记得有一次是某个变量名大小写写错了,Node.js 直接抛ReferenceError,进程起不来。这种情况改回变量名,再重启就好了。

5.2 端口被占用导致启动失败

如果你的wework-verify.js会监听某个端口,可能重启时提示EADDRINUSE。这说明上一次的进程没有完全退出,或者有别的进程占了端口。

解决方案:

lsof -i :端口号

找到对应 PID 杀掉,再重启服务。如果不确定端口号,看脚本里listen或者服务配置用的哪个端口即可。

5.3 更新完后运行结果和预期不一致

这种情况最折磨人,因为进程明明是好的,逻辑也改了,但效果没变。大概率是你重启错了进程。比如你之前用 pm2 启动的,但更新后用 nohup 又拉了一个,两个进程同时在跑,请求被旧的进程处理了。

排查命令:

ps -ef | grep wework-verify

如果看到两个以上的node wework-verify.js进程,说明出现了多实例并存的情况。处理办法是把所有相关进程全部杀掉,然后用你常用的一种方式统一拉起来。多进程并存这个问题,我在实际项目里踩过好几次,根源就是有时候图省事换了一种启动方式,结果新旧进程都在跑,测试结果时好时坏。

5.4 后台进程在终端关闭后自动退出

这其实是 nohup 用户最常见的困惑。如果你用了:

node wework-verify.js &

那当你关闭终端的时候,进程会收到 SIGHUP 信号然后退出。所以正确姿势是加nohup:

nohup node wework-verify.js > app.log 2>&1 &

nohup的作用就是忽略挂断信号,让进程在终端关闭后继续运行。

5.5 pm2 启动报错,提示 app 不存在

如果 pm2 列表里空空如也,但你又确实用 pm2 启动过,可能你启动时没指定--name,pm2 用脚本路径作为标识,不同路径下启动会被认为是不同应用。建议统一规范,启动时都指定名字:

pm2 start wework-verify.js --name wework-verify

5.6 日志文件变得巨大

长期跑的服务日志会越来越大,磁盘被日志撑爆也是时有发生的事。日志文件过大还会导致日志写入变慢,间接影响性能。建议定期清理或按天切割日志,pm2 有内置的日志轮转功能:

pm2 install pm2-logrotate

如果是 nohup 方式,可以简单粗暴地在 crontab 里定期清空日志:

0 0 * * * truncate -s 0 /path/to/app.log

这个命令不会删除文件,只是把内容清零,进程的日志句柄不受影响,实测可用。

6. 额外建议:与其反复重启,不如一开始就选对工具

如果你现在用的是 nohup 方式,我真心建议你迁移到 pm2。原因很简单:nohup 方式下,进程一旦崩溃不会自动恢复,你只能手动登录服务器看日志排查。而 pm2 提供了守护进程的能力,进程挂了会自动拉起,还带了日志聚合、内存监控、开机自启等功能,长期跑任务能省下大量精力。

迁移过程不复杂,先杀旧进程,再执行:

pm2 start wework-verify.js --name wework-verify pm2 save pm2 startup

pm2 save会把当前进程列表保存下来,pm2 startup会生成开机自启脚本,这样服务器重启之后 pm2 会自动拉起服务,不需要人工干预。

这里有一个实际体验过的细节必须提一下:pm2 startup输出的那串命令,是需要你在当前用户下执行的。直接复制它输出的完整命令粘贴回车即可,不用动 sudo,否则环境变量容易串掉。

用上 pm2 之后,更新文件就永远只需要一条命令了:

pm2 restart wework-verify

再也不用手动找 PID、杀进程、重新拉起了。

7. 我踩过几次坑之后的固定操作习惯

现在我每次更新wework-verify.js,操作流程已经固定下来了,有点像肌肉记忆:

第一步,node --check wework-verify.js做语法检查,这是底线。 第二步,cp备份原文件,带时间戳。 第三步,执行更新命令,pm2 的话就是pm2 restart wework-verify。 第四步,等两三秒,pm2 status看状态,pm2 logs wework-verify --lines 30扫一眼日志里有没有异常。 第五步,实际触发一次业务功能,确认新逻辑跑通。

这套流程看起来多几步,但每一步都是为了少踩一次坑。尤其是语法检查那一步,真的能拦住一半以上的低级错误。如果你现在正被“更新文件没生效”这个问题困扰,对照我上面说的方式,先确认自己属于哪种启动方式,再执行对应的重启命令,基本就能解决。

最后再说一个容易被忽略的小点:如果你是通过 FTP 或者文件管理器直接上传替换服务器上的wework-verify.js,上传完成后确认一下文件大小和本地是否一致。传输中断导致文件不完整的情况我也遇到过,那时候代码看着没问题,但一运行就是各种神奇的报错。用ls -l wework-verify.js看看大小,再对比本地文件,一眼就能识别出问题。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进