资讯详情

Node.js服务部署必备:PM2进程管理与实战手册

📅 2026/10/20 3:16:03 | 华诺云谱 👁 阅读
Node.js服务部署必备:PM2进程管理与实战手册
1. 为什么Node服务都需要一个PM21.1 新手最常见的三大崩溃现场我先说三个场景如果你已经遇到过了那这篇手册你大概率是需要的。第一个场景你在本地把接口调通了node app.js跑得挺好兴冲冲部署到服务器上。结果SSH窗口一关服务就没了。为什么因为进程是挂在你的终端会话下面的终端退出会话挂断子进程被系统回收。这是新手第一次意识到“原来这样跑不行”。第二个场景服务半夜崩了。你在睡梦中用户已经骂了一轮又一轮第二天早上才发现进程不在了。你翻开日志发现是某个未捕获的异常把进程直接带崩但关键是你不知道它什么时候崩的也不知道怎么让它自己活过来。第三个场景一个Node实例单线程跑着CPU只用一个核前端一旦有稍微高一点的并发请求响应时间肉眼可见地涨。你想多开几个实例但手动node app.js开八个窗口这不现实而且端口也会冲突。这三个问题本质上是同一件事Node进程缺少一套系统化的守护和管理方案。PM2解决的就是这个问题。它不仅能在进程退出后立刻拉起新进程还能收集日志、做负载均衡、设置内存上限、配置开机自启。对于刚接触服务器部署的新手来说PM2几乎是最短路径上必经的一个工具。1.2 PM2解决的不只是“重启”这一件事很多人对PM2的理解停留在“进程守护”四个字上觉得它就是一个自动重启的工具。这是低估了它。PM2的核心是一个完整的进程生命周期管理方案守护只是其中最基础的一项。我把PM2的核心能力拆成四层第一层进程托管。pm2 start app.js之后进程进入PM2的监管列表。退出自动拉起崩溃自动重启这个逻辑是默认开启的不需要你做额外配置。第二层日志管理。PM2会把进程的stdout标准输出和stderr标准错误输出分别收进日志文件并带有时间戳。你不需要自己写fs.appendFile去记日志也不需要借助forever那种原始方案一条pm2 logs命令就能实时看到输出。第三层负载均衡。PM2内置的cluster模式可以让你一行命令把服务以多实例方式跑起来利用服务器多核CPU的能力并在实例之间分发请求。当年很多Node服务扛不住压力其实不是代码问题而是根本没有用上多核能力。第四层运维自动化。包括开机自启、优雅重启、内存监控、CPU监控、部署流程配合。这些都是生产环境必须的东西也是新手最容易忽略的。这篇手册我按“先会用再理解最后排查”的路线来写。所有命令我都按实际场景来演示你跟着敲就行。2. 安装与第一条启动命令2.1 安装前的环境准备PM2本身是一个npm全局包安装它之前你需要确保Node环境已经就绪。Node的版本建议8以上实际上我见过有人在Node 6上跑PM2也能用但新版PM2特性比如某些监控指标还是需要新版本支持。我自己的习惯是保持Node LTS版本避免不必要的兼容性问题。安装命令很简单npm install -g pm2装完之后验证一下pm2 --version如果能看到版本号说明安装成功。这里有一个小坑有些服务器的npm全局bin目录不在PATH里安装完会提示command not found。解决方式是查看npm prefix -g输出的目录把它加到PATH里。这个情况在服务器上比本地开发环境更常见。如果你用的是yarn或者pnpm对应命令是yarn global add pm2 pnpm add -g pm2安装完成后还可以给PM2开启启动时的自动补全这个看个人习惯我是一直用着pm2 completion install然后重开一个终端才会生效。这个补全对新手其实挺有用的能帮你少打不少命令。2.2 第一条启动命令与通用命令速查准备工作做完我们进入正题。假设你的项目入口文件是app.js目录是/var/www/myapp启动命令就一句话cd /var/www/myapp pm2 start app.js --name myapp--name是给进程起一个别名方便后续管理。如果不指定PM2会用文件名作为默认名称。启动之后你会看到一张表格里面有几个关键信息id进程IDPM2内部管理的编号从0开始。name进程别名。modefork或cluster目前是fork模式。statusonline就是正常运行stopped是已停止errored是异常退出。restarts重启次数。这个很关键后面排查崩溃问题就看它。启动只是第一步接下来是几个高频命令你最好背下来pm2 list # 查看所有进程列表 pm2 logs myapp # 实时查看日志 pm2 restart myapp # 重启进程 pm2 reload myapp # 优雅重载进程 pm2 stop myapp # 停止进程进程保留在列表里 pm2 delete myapp # 删除进程从列表里移除 pm2 monit # 进入实时监控面板这里区分一下restart和reload。restart是直接杀掉进程再启动会有一瞬间的断连reload是针对cluster模式下的0秒停机重启它会逐个替换实例滚动式完成。如果是fork模式reload和restart的表现基本一致没有太大区别。还有一个细节pm2 start app.js这一步PM2默认会把进程名称设置为app。如果你同时管理多个项目每个项目入口恰好都叫app.js就很容易搞混。所以从一开始就养成加--name的习惯后面省心很多。3. 看懂PM2的进程状态3.1 status字段代表什么新手最容易困惑的就是pm2 list里那几个英文单词。我一个个说。online进程活着正常接受请求。这个没什么好说的。stopped进程被手动停止了。执行pm2 stop myapp就是这个状态。注意此时进程还在PM2的列表里你可以随时pm2 start myapp再次拉起它。如果你执行pm2 delete myapp那就是彻底移除进程没了列表里也没了。errored进程异常退出了。这个状态要重视说明进程跑了但没跑起来可能是有未捕获的异常也可能是端口被占用。大多数时候PM2会在它退出后尝试重启你会发现restarts数字一直在涨。如果涨得很快说明有严重问题比如端口被占或者配置加载失败。还有一个状态叫stopping一般是正在停止的过渡状态不用太在意。每次启动或重启后建议看一眼这几点status是不是onlinerestarts是不是在增长uptime是不是已经持续了一段时间。如果restarts从0变成几十那基本可以判定存在反复崩溃的循环。这时候别在pm2 restart上纠结要想办法看日志找根因。3.2 日志查看与日志切割我把日志单独拉出来讲因为它对排查问题太重要了。PM2默认会把进程的stdout写进~/.pm2/logs/myapp-out.logstderr写进~/.pm2/logs/myapp-error.log。如果默认路径找不到可以用pm2 info myapp查看详情里面会列出两个日志文件的完整路径。实时看日志的命令pm2 logs myapp这个命令会进入一个实时滚动的视图类似tail -f的效果能看到所有的输出。如果想看最近的200行pm2 logs myapp --lines 200不加--lines的话默认会给最近20行。这个参数在生产环境排查问题时很好用日志太长了就指定行数节省时间。这里有个坑要提醒很多新手在自己的代码里用console.log输出内容发现pm2 logs里看不到。大部分情况是因为进程还没把缓冲区的数据刷出来特别是console.log输出量很小的时候缓冲区没满就不会主动写入。处理方法有两个一是用pm2 flush清空缓冲区二是启动时加--merge-logs参数。其实更推荐在代码里用成熟的日志库比如log4js或winston直接把日志写入文件这样更可控PM2这边只需要管理文件大小。日志文件无限增长是个大问题。默认情况下PM2不会按大小切割日志时间长了你的磁盘会被占满。解决办法是安装PM2的日志切割模块pm2 install pm2-logrotate这个模块可以让日志按天轮转也可以设置大小上限。常用配置pm2 set pm2-logrotate:max_size 50M pm2 set pm2-logrotate:retain 7max_size是单个日志文件超过多大就切割retain是保留几天的历史文件。我建议一上来就配好这两个参数免得一个月后发现磁盘被日志塞满了。4. 生产环境的配置文件4.1 ecosystem.config.js每次都用pm2 start app.js来启动进程少的时候还行一旦进程多了你会发现命令行参数长得没法看要指定--name、要指定NODE_ENV、要指定--max-memory-restart、还要指定--watch。这种情况下配置文件就是必需品。PM2支持的配置文件格式是JavaScript默认文件名ecosystem.config.js。一个简单的配置长这样module.exports { apps: [ { name: myapp, script: ./app.js, instances: 1, exec_mode: fork, env: { NODE_ENV: production, PORT: 3000 }, max_memory_restart: 300M, log_date_format: YYYY-MM-DD HH:mm:ss, error_file: /var/log/myapp/error.log, out_file: /var/log/myapp/out.log } ] };然后启动命令变成pm2 start ecosystem.config.js这个配置文件的好处是明显的环境变量、日志路径、重启策略写在一起团队协作时大家看到的是同一份配置不会因为你少了一个参数导致生产环境和本地环境行为不一致。我见过不少生产事故根子就是启动命令在文档里和实际操作里对不上。配置文件能从根本上避免这个问题。文件里的env字段代表默认环境变量。如果你有多套环境可以这样拆module.exports { apps: [ { name: myapp, script: ./app.js, env: { NODE_ENV: development, PORT: 3000 }, env_production: { NODE_ENV: production, PORT: 8080 } } ] };启动生产环境时加--env productionpm2 start ecosystem.config.js --env production注意--env production后面的production对应配置文件里的env_production字段PM2会把这个对象里的字段合并进环境变量。这个机制是固定的env字段是基础env_xxx字段按需覆盖。4.2 cluster模式与实例数怎么定这是PM2最值得花时间理解的一个功能。Node.js是单线程的默认情况下一个进程只能跑在一个CPU核心上。如果你的服务器是4核的一个Node进程最多只能吃到其中一核的性能剩下三核闲着。这在并发量上来之后是明显的瓶颈。PM2的cluster模式可以让你把一个应用复制成多个进程实例每个实例绑定到不同核心由PM2内置的负载均衡器把请求分发到各个实例上。配置方式module.exports { apps: [ { name: myapp, script: ./app.js, instances: 4, exec_mode: cluster } ] };启动后你会在列表里看到4个进程共享同一个名字和ID段。instances填几最合适这个问题没有标准答案但我可以给你一个经验法则先看服务器是几核的先按核数填然后压测。如果CPU没有跑满就逐步增加同时观察内存。填-1或max的话PM2会自动使用服务器CPU的核心数作为实例数instances: max这个写法适合大多数场景但有一个前提你的应用必须是无状态的或者说状态可以外部化。比如session不能存在内存里要放到Redis文件上传不能写到本地磁盘要放到对象存储。否则cluster模式下不同实例之间数据不同步你会遇到各种“不是必现但很致命”的诡异问题。还有一点要强调cluster模式只适用于HTTP服务因为它依赖PM2内置的端口共享机制。如果你的脚本是定时任务、消息队列消费者这种不监听端口的进程不要用cluster模式fork模式一个实例就够了多开反而会造成任务重复处理。4.3 内存限制与自动重启Node进程有个常见问题内存泄漏。泄漏到一定程度进程会变得非常卡甚至被系统杀掉。PM2提供了max_memory_restart参数来主动干预当进程内存超过设定值自动重启。max_memory_restart: 300M这里怎么取值我个人的习惯是先正常跑一段时间观察基准内存然后在这个基础上加50%作为阈值。比如你的服务稳定运行时占用150M内存那就设成300M留出一定的缓冲。如果你设置的阈值刚好卡在正常波动范围内会导致频繁重启反而不稳定。重启策略是超过阈值后PM2会发送一个重启信号进程被重新拉起。但这只是治标内存泄漏的根因还是在代码里。实际遇到泄漏问题时先用pm2 monit观察内存曲线确认是平滑上涨还是阶梯式增长然后结合日志和代码逐步排查。5. 开机自启与开机任务5.1 自启原理服务器重启之后PM2管理的进程不会自动恢复。如果你没有配置开机自启系统重启后你的服务就全挂了而且你可能要等到用户反馈才知道。对新手来说这个坑几乎是必踩的。PM2解决这个问题的方案很聪明它生成一个系统服务脚本交给系统的初始化系统systemd来管理。这样开机时systemd会启动一个PM2守护进程然后由守护进程恢复所有之前管理的进程列表。执行下面的命令pm2 save pm2 startuppm2 save是把当前进程列表快照保存到~/.pm2/dump.pm2这是重启后恢复的依据。pm2 startup会检测当前的初始化系统输出一条需要执行的命令你按提示复制执行即可。如果是systemd环境涉及的命令大致是sudo env PATH$PATH:/usr/bin pm2 startup systemd -u 用户名 --hp 用户主目录执行完可以验证一下systemctl status pm2-用户名看到active (running)就说明成功了。如果状态不是active大概率是用户或路径不对重新执行pm2 startup仔细看它提示的那条命令。5.2 实操配置完整的配置流程我建议这么做第一步把项目启动好确保一切正常。第二步执行pm2 save保存当前进程快照。第三步执行pm2 startup按提示完成systemd服务注册。第四步手动重启服务器或者用systemctl reboot验证进程是否自动恢复。有一个操作顺序要特别注意先pm2 save再pm2 startup还是反过来严格来说都行但我的建议是先把服务跑起来、确认没问题再保存、再配置自启。因为pm2 save保存的是“当前正在跑的进程列表”如果列表是空的那你保存的就是一个空快照重启后什么都不会恢复。还有一个容易忽略的点如果你后面新增了进程必须重新pm2 save否则新增的进程不会出现在自启列表里。我见过有人配置过一次自启之后后面加的进程重启后全部消失就是因为漏了这一步。6. 常用进阶功能6.1 负载监控pm2 monit是我在生产环境用得很频的交互界面。执行后你会看到每个进程的CPU、内存、事件循环延迟等信息还有日志实时输出。监控面板里最有价值的是“自定义指标”区域比如HTTP请求处理量、并发连接数前提是你在代码里用PM2的API上报const pm2 require(pmx).init();这个属于进阶玩法新手阶段可以先不用管但了解一下有好处PM2不只是被动地看进程状态它还能主动采集业务层的指标。把这些指标接到监控系统里你就有了完整的可观测性。如果你不满足于终端里的monitPM2还有一个Web界面通过pm2 plus开启它是商业化服务支持远程监控、告警、异常检测。个人项目或小团队不一定要用但如果你管着几十台机器这种集中式监控的价值就很明显了。6.2 零停机部署重载在cluster模式下你改了代码要重新加载请用reload而不是restartpm2 reload myappreload的逻辑是逐个重启实例。假设你有4个实例PM2会先重启第一个等它起来并且健康检查通过再重启第二个直到全部替换完成。这个过程不会出现服务整体不可用的窗口用户请求始终能被处理。对比一下restart它会一次性把所有实例全部停掉再全部启动中间有个时间窗口是服务不可用的。对于生产环境来说这个窗口哪怕只有几百毫秒也可能造成请求失败。如果你用的是fork模式reload没有实际意义它和restart差不多因为只有一个实例没法滚动。所以这也是为什么我建议生产环境尽量用cluster模式。部署更新时我一般这样做先构建新版本如果有构建步骤然后pm2 reload ecosystem.config.js等所有实例滚动更新完再验证接口。整体过程几十秒内完成用户无感知。7. 常见问题排查实录7.1 进程反复重启现象pm2 list里restarts数字不断增加或者status一直在errored和online之间横跳。第一步看日志。命令是pm2 logs myapp --err --lines 100重点看有没有SyntaxError、Module not found、EADDRINUSE、TypeError这类关键词。大多数反复重启都是这几个原因代码语法错误、依赖没装全、端口被占、环境变量缺失。第二步如果是语法错误本地先修好再部署。你可能会觉得这是废话但实际上很多人先启动再看日志然后又去改代码改完只重启进程不重新构建导致代码没变。记得有构建步骤的一定要先构建。第三步如果是依赖缺失npm install重新装一遍确保node_modules完整。7.2 端口被占用启动时报EADDRINUSE说明端口被某个进程占用了。先用下面的命令找到占用进程lsof -i :3000或者netstat -tunlp | grep 3000找到PID后确认这个进程是否还需要。如果不需要就杀掉然后重启你的应用。如果你确定这个端口应该给PM2管理检查一下是不是上一个进程还没完全退出比如pm2 stop之后端口没有立即释放等几秒再启动。7.3 环境变量不生效在ecosystem.config.js里配了env但代码里读到的却是undefined或者旧值。这个大概率是缓存问题。PM2会缓存环境变量。改动配置文件后重新启动时用pm2 restart myapp --update-env还要注意pm2 restart和pm2 start在加载环境变量上的行为不完全一样。restart会保留启动时的环境start会重新读取配置文件。所以改完配置后建议用pm2 delete myapp再pm2 start ecosystem.config.js比restart更干净。另外配置文件里的env_production只有你在命令里加了--env production才会加载不加就永远只有env里的字段。这个坑我踩过不止一次。7.4 日志找不到如果你配置了自定义日志路径比如/var/log/myapp/error.log但是文件一直没生成大概率是目录权限问题。PM2以当前用户身份运行要去写/var/log下的目录需要那个目录有写权限。遇到这种情况我的建议是把日志目录放在应用目录下比如/var/www/myapp/logs然后用error_file和out_file指向它error_file: /var/www/myapp/logs/error.log, out_file: /var/www/myapp/logs/out.log改完配置重启之后看日志文件有没有生成。如果还是没有用pm2 logs看看有没有报错信息定位是路径问题还是权限问题。最后再说一个我自己的经验PM2虽然好用但它不是万能的。进程守护、日志收集、负载均衡、开机自启这些都是基础设施层面的保障真正决定服务质量的还是代码本身。一个死循环的代码PM2再怎么重启也会被下一个死循环拖垮一个内存泄漏不修的应用max_memory_restart只会让它陷入重启掉线又重启的循环。把PM2当成一个可靠的工具用好它但不要依赖它来掩盖代码的毛病。先学会用命令再学会看状态然后学会配配置文件最后你就能把这些东西串起来搭出一套基本让人放心的Node服务部署体系。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑