首页 / 资讯中心 / 文章详情

Node服务生产环境进程守护:PM2集群、日志与自启实践指南

Node服务生产环境进程守护:PM2集群、日志与自启实践指南 ★ FEATURED ARTICLE
1. 为什么 Node 服务上线后离不开进程管家1.1 一次让我印象深刻的深夜发布早几年我还在用node app.js直接跑服务的时候踩过一个大坑在服务器上把 Node 服务启动起来测试一切正常然后顺手把 SSH 终端关掉准备下班。结果第二天一到公司群里已经有人嚷嚷后台服务打不开了。我登录服务器一看进程早就没了而当天晚上根本没有任何人会去重启它。这就是纯 Node 进程和真正的“线上服务”之间最大的鸿沟。你总不能在每次断电、崩溃、或者终端会话断开之后都靠人力去服务器上敲一次启动命令。生产环境需要的是一个能一直在后台值守的进程管家服务崩了它自动拉起来日志被统一收走开机之后不用人工干预也能恢复。PM2 就是干这个的而且几乎是 Node 生态里最通用、最省心的方案。这篇文章不打算写成一份照搬官方文档的说明书而是按新手最容易遇到的理解盲区来讲。读完你应该能回答这几个问题为什么不能让 Node 裸奔、PM2 的常用命令分别是什么含义、日志和开机自启怎么配置、集群模式到底解决了什么以及线上遇到“进程明明在线但服务就是不通”的时候应该怎么排查。1.2 PM2 帮我守住了哪三件事我在带新人时经常说PM2 的核心工作可以压缩成三件事守护、日志、自启。剩下的命令和配置基本都是围绕这三件事展开的。守护进程被启动之后PM2 会一直盯着它。进程异常退出PM2 按设定好的重启次数把它重新拉起来服务器重启后PM2 还能手动或自动把之前的进程列表恢复回来。日志Node 进程的标准输出和标准错误PM2 会分别写入日志文件。想查问题的时候一条pm2 logs就能看到不用再自己写 fs 流去追加文件。自启在服务器上开机后自动拉起 PM2 以及它管理的所有服务。配合pm2 save整个恢复过程基本能做到无感。这些能力听起来简单但如果你自己写一个 shell 脚本去实现“崩溃重启 日志收集 开机启动”会发现要处理很多边角情况比如进程假死、重启频率控制、日志轮转。PM2 把这些做成了成熟的功能所以我们没必要重复造轮子直接规范地用起来就好。2. 装好与跑通从全局安装到第一条 start 命令2.1 全局安装与版本确认安装 PM2 很简单一条 npm 全局安装命令就够了npm install -g pm2装完先确认版本多留个心眼能避免以后踩到“解释器版本不对”的坑pm2 --version这里有个新手容易忽略的点如果你是用 root 用户直接全局安装后续 PM2 生成的 startup 服务也会归属于 root。这在大多数单机部署场景下没有太大问题但如果你有多个项目、多个系统账号最好还是用专门的应用账号来安装和运行。用普通账号跑服务的最大好处是权限边界更清晰万一代码有漏洞被攻破后的影响范围也会小一些。我一般建议用nvm管理 Node 版本再用nvm所对应的 node 环境去全局安装 PM2这样后续每个项目的依赖版本也能独立隔离。2.2 第一条 pm2 start终端里到底发生了什么先找一个小项目试一下比如你的项目入口文件是server.js那么执行pm2 start server.js执行完之后屏幕上会出现一个表格这些字段要能看懂字段含义idPM2 分配给进程的内部编号name进程名称默认来自脚本文件名modefork 或 cluster单进程默认 forkstatusonline / stopped / erroredrestart进程被 PM2 重启过的次数cpu当前 CPU 占用百分比mem当前内存占用uptime进程已运行时间第一次跑起来之后你可能会好奇终端关掉之后进程还在不在答案是还在。PM2 启动的进程已经脱离你的 SSH 会话变成了系统里的守护进程。你可以退出终端再用pm2 list去看状态仍然是 online。不过用这种最原始的方式启动进程名默认是脚本名比如server。如果以后同时管理很多服务最好从一开始就给进程起一个有意义的名字pm2 start server.js --name demo-api这样后面查看日志、重启、删除的时候都是通过名字操作而不是靠 id 去猜。2.3 把参数写进 ecosystem.config.js从“能跑”到“可控”环境变量、最大内存限制、日志路径如果每次都靠命令行参数去拼很容易出错而且换一台机器部署时没法复用。PM2 提供了配置文件通常叫ecosystem.config.js把服务的启动参数固化下来module.exports { apps: [ { name: demo-api, script: ./server.js, instances: 1, exec_mode: fork, env: { NODE_ENV: production, PORT: 3000, }, max_memory_restart: 300M, out_file: ./logs/out.log, error_file: ./logs/error.log, merge_logs: true, time: true, }, ], };字段含义不难理解script指定入口文件env注入环境变量max_memory_restart设置内存上限out_file和error_file指定日志输出位置merge_logs把多个实例的日志合并到同一个文件time则在每条日志前加上时间戳。然后通过配置文件启动pm2 start ecosystem.config.js我习惯把配置文件放进项目仓库里这样团队里任何人都能用同一套参数起服务。你没看错它就是项目的一部分和package.json一样值得被版本管理。3. 高频命令的语义边界restart、reload、stop、delete 别用错3.1 restart 与 reload平滑重载到底在什么时候有用命令里最容易混淆的就是 restart 和 reload。它们的字面意思很接近但实际行为差别很大。pm2 restart demo-api的含义是先把旧进程停掉再启动新进程。这个过程中会存在短暂的服务断开可能几十毫秒也可能几百毫秒。对于内部后台管理系统、延迟敏感度不高的 APIrestart 完全够用。pm2 reload demo-api则是平滑重载PM2 会逐个重启集群里的实例。具体来说它先启动一个新实例等新实例接管流量后再把旧实例退出这样一轮轮滚动下去整个过程中始终有实例在提供服务。reload 的前提是进程必须工作在 cluster 模式下也就是instances大于 1并且exec_mode为cluster。如果你在 fork 模式下执行 reloadPM2 会直接提示你它做不到。所以我的建议是开发环境随便 restart生产环境如果服务需要保持流畅体验尽量用 reload。但要注意reload 要求代码本身是无状态或能够容忍会话切换的后面会说集群模式时再展开。3.2 stop 与 delete暂停还是彻底移除这两个命令也容易混。pm2 stop demo-api只是让进程暂停进程仍然留在 PM2 的进程列表里状态变成 stopped。你可以随时再用pm2 start demo-api把它重新拉起来。整个过程不会丢失原来的日志配置和启动参数。pm2 delete demo-api则是彻底把进程从 PM2 的管理列表中移除同时会清理掉它对应的日志文件。这个操作不可逆如果之后还想再启动必须重新执行pm2 start或重新用配置文件启动。我建议把 stop 当作“临时维护”按钮把 delete 当作“下线项目”按钮。临时需要停服做数据库迁移用 stop某个服务确定不干了才用 delete。删之前如果有想保留的日志一定要先复制出去否则日志文件会被一并清理。3.3 状态查询三件套list、info、monit日常运维里我很少只靠pm2 list看状态更多是这几个命令搭配用pm2 list pm2 info demo-api pm2 monitpm2 list是概览适合快速看有多少服务在线、重启次数是否异常。pm2 info demo-api会展示这个进程的详细描述包括可执行文件路径、cwd、环境变量、重启次数、日志路径排查问题时非常有用。pm2 monit会进入一个实时刷新的监控界面能看到 CPU、内存、日志流适合压测或排查资源占用异常时盯着看。还有一个容易被忽略的命令是pm2 flush作用是清空当前所有日志缓冲。我不建议盲目去清但当你确定日志已经备份过、或者确实不需要旧日志时它能让排查现场更干净。我不太建议新手一开始就把所有命令都背下来。先记住 start、list、logs、restart、stop、delete 这几个再逐步扩展到 reload、startup、save就能覆盖绝大多数日常操作。4. 日志、开机自启与崩溃保护生产环境三件套4.1 日志去哪了先知道文件名再谈分析很多新手执行node app.js的时候日志是直接打到终端的关了终端就什么都看不到了。PM2 的做法是把标准输出和标准错误分别写进不同的文件默认的位置在~/.pm2/logs/目录下如果你的进程名是 demo-api一般会看到~/.pm2/logs/demo-api-out.log ~/.pm2/logs/demo-api-error.log查日志最常用的命令是pm2 logs demo-api这会实时跟随并输出日志流适合现场观察。如果只想看最近的 200 行可以加参数pm2 logs demo-api --lines 200这里我要专门说一个实践细节如果你在配置文件里只写了out_file而没写error_filePM2 会把错误日志放进默认的 error 文件里不会自动和 out 文件合并。线上排查时最容易忽略的就是“我明明看了 out.log怎么没有报错”然后才想起来报错其实在另一个文件里。所以ecosystem.config.js中最好把out_file和error_file都显式配好并放在同一个日志目录下查问题时方便一起看。4.2 开机自启pm2 startup 与 pm2 save 的搭配方式把服务部署到服务器后最怕的一件事就是机房断电系统重启服务没有跟着起来。PM2 的解决办法是生成一个开机自启服务。首先生成系统服务配置pm2 startup这条命令会根据当前系统初始化系统生成一段提示并让你复制执行。有些版本会直接帮忙执行有些版本会提示你手动复制到终端里执行。关键点是pm2 startup执行完还不够你还需要把当前的进程列表保存下来pm2 savepm2 save会把当前 PM2 管理的进程列表快照保存到一份持久化文件中。下次系统开机时startup 服务会自动执行 PM2然后 PM2 根据这份快照把所有进程恢复起来。如果没有 save即使 pm2 开机运行了它也一无所知不会帮你拉回任何服务。我踩过的一个坑是换服务器之后忘了在新机器上执行pm2 save结果每次重启都得手动一个个把服务拉起来。后来我把“执行 startup、执行 save、重启验证一次”写进了部署脚本才彻底根治。你可以在新机器上重启之前先测一遍确认开机后pm2 list的状态是不是 online。4.3 崩溃保护自动重启的触发条件与配置PM2 默认会在进程异常退出时自动执行重启。这个逻辑很简单只要进程不是因为主动执行 stop 而退出PM2 就会尝试重新拉起。但这里有个容易误导新手的地方不是所有“看似崩溃”的情况都会立刻重启。比如进程因为内存不足被系统杀掉PM2 会认为这是异常退出于是重启。但如果你的代码里有一个未捕获的同步异常Node 进程直接退出PM2 同样会重启。真正需要关心的是“无限重启循环”进程启动后马上崩溃PM2 拉起来之后又崩溃碰到这种情况重启次数会一直上涨而服务始终起不来。这时候可以看pm2 list里的 restart 字段。如果重启次数在短时间内不停增加基本可以判断是代码启动阶段就有问题。排查步骤通常是用pm2 logs demo-api --err看错误日志。手动在前台跑一次node server.js直接暴露完整报错。修复后执行pm2 restart demo-api。另外max_memory_restart是一个很实用的保护参数。很多 Node 服务在达到某个内存水位后性能会急剧下降与其让它在崩溃边缘挣扎不如设置一个上限让 PM2 主动重启它pm2 start server.js --name demo-api --max-memory-restart 300M这个值要根据实际业务定。如果服务日常内存就在 200M 左右峰值 400M那设置 300M 就会频繁重启反而不稳定。我一般会先观察一段时间再用稳定基线的 1.5 到 2 倍作为上限。5. 集群模式与资源限制从单进程到多核利用5.1 集群模式能带来什么-i max 的取舍Node 是单线程的默认情况下一个进程只能用到一个 CPU 核心。现代服务器动辄八核、十六核如果你只跑一个进程其余核心都在围观资源利用效率自然上不去。PM2 的集群模式可以帮你把 Node 服务启动多个实例由 PM2 负责负载均衡。启动时这样写pm2 start server.js -i max-i max的意思是创建和 CPU 核心数相同数量的实例。机器有 4 核PM2 就启动 4 个实例有 16 核就启动 16 个。多个实例监听同一个端口PM2 会把进来的请求轮询分发到不同实例上。但这里有一个很重要的前提你的应用代码必须能承受多个进程同时运行。比如如果服务内部用内存保存用户登录状态集群模式下这些状态会被分散到多个进程里用户在 A 实例登录下一次请求被分到 B 实例就会丢失会话。解决思路是把会话状态放到 Redis 或数据库这类共享存储里让每个实例都从同一个地方读写状态。所以我的建议是无状态的服务放心开集群有状态的旧项目先别只为了利用多核去开集群否则会引入一堆诡异的“偶现”问题。5.2 max_memory_restart 与环境变量配置集群模式下资源限制配置要按道理进行一次整体设计。比如一台 8 核、16G 内存的机器如果开 8 个实例每个实例的内存上限设置成 1G那么理论上最坏情况会占用 8G 内存留出系统和其他进程的空间。具体配置可以这样写module.exports { apps: [ { name: demo-api, script: ./server.js, instances: max, exec_mode: cluster, max_memory_restart: 1G, env: { NODE_ENV: production, PORT: 3000, }, }, ], };这里exec_mode: cluster是集群模式的关键字段instances: max等价于-i max。如果你的机器是 8 核却只希望启动 4 个实例可以写成instances: 4,环境变量也是踩坑重灾区。部署时最容易出现的是本地.env文件里的变量在生产环境没被带上。PM2 的env字段只负责传环境变量它不会自动读取项目根目录下的.env文件。如果你依赖.env需要在启动命令里显式加载或者把变量直接写进ecosystem.config.js。我曾经因为一个数据库连接串没写进配置导致生产环境连接了错误的库查了半天才发现是环境变量没同步。5.3 常见的自动化部署组合当项目逐渐多起来每次手动登录服务器、手动 restart效率太低。一个很常见的做法是把 PM2 命令写进发布脚本里。#!/bin/bash npm ci npm run build pm2 reload ecosystem.config.js --env productionpm2 reload ecosystem.config.js相当于是“以配置文件为准没有的新增、已有的重载”适合作为发布流程的统一入口。注意这里用的是 reload 而不是 restart就是为了避免发布时出现断流。我还见过直接把本地 node_modules 打包上传到服务器、结果二进制依赖不兼容的案例。如果项目引入了编译型依赖建议在服务器上执行安装或者用 CI 打包成一致的产物再上传。这块和 PM2 本身无关但部署时最浪费时间的问题往往不在 PM2而在构建流程不一致。6. 新手最容易踩的坑和排查路径6.1 进程状态 online但服务怎么也访问不了这是一个高频问题很多新人一看到pm2 list里 status 是 online就认定服务没问题结果浏览器一访问就超时。排查顺序应该从外到内先在服务器本地测一次curl http://127.0.0.1:3000/health如果通说明服务本身是好的问题出在防火墙、安全组或反向代理。看端口监听情况ss -lntp | grep 3000确认监听地址是0.0.0.0还是127.0.0.1。如果监听在127.0.0.1外部自然访问不了。查看日志pm2 logs demo-api --err确认有没有运行时异常。确认代理转发配置如果使用了 Nginx 之类的反向代理检查转发目标和 PM2 里的端口是否一致。我遇到过好几次Nginx 转发到 3000PM2 的进程因为配置改动已经换到了 3001结果全部请求都 502。一句话总结状态 online 只能说明“进程活着”不代表“网络通路通了”。6.2 重启服务器后 PM2 没生效没执行 pm2 save有次我帮一个同学排查问题他说自己在服务器上配好了 PM2进程也跑得好好的但只要一重启服务器服务就消失。我登录上去看了一遍pm2 startup确实执行过但pm2 save没做。这种情况非常典型。startup 只是设置了“开机后 PM2 要运行”的系统服务而 PM2 运行之后该恢复哪些进程靠的是最近一次pm2 save生成的快照。这两个动作是配套的少了一个都不行。更隐蔽的一个问题是你在不同用户下执行了 startup 和 save导致重启后服务在以另一个用户身份运行可能因为目录权限不足而失败。所以无论是安装、启动还是配置自启最好都统一到同一个系统账号下别 root 一下、应用账号一下地来回折腾。6.3 老旧日志撑爆磁盘PM2 默认不清理归档PM2 会一直往日志文件里追加内容如果没有外部机制清理时间一长日志文件可能会膨胀到几个 GB直接把磁盘写满。线上服务突然挂掉查到最后发现是磁盘满这种事一点都不罕见。解决方向有两个在ecosystem.config.js里为每一个服务配置独立的日志路径方便后续统一清理。给 PM2 装上日志轮转插件让日志按大小或天数自动分割。轮转插件的使用非常简单安装并设置参数后PM2 会自动按规则清理历史日志。实际使用中我会保留最近 14 天或者最近 500M 的日志超过的部分自动压缩归档。这样既保证有足够的历史数据可以排查又不会把磁盘无限撑大。另外日志里如果全是运行时输出也可以考虑在业务代码里合理控制日志级别避免把所有调试信息都打到生产环境。日志不是越多越好关键是要在需要排查时有据可查。6.4 无限重启循环怎么处理最后再补一个实战场景进程重启次数涨得很快但日志里没有明显报错。这种情况很多时候是代码启动阶段没有任何输出静默退出了比如连不上数据库但没打日志。处理思路是手动把前台方式跑一遍直接看终端的完整输出。PM2 在管理进程时会吞掉一部分前台可见的信息手动启动反而更容易暴露问题node server.js等代码确认能稳定启动再放回 PM2 跑。如果确实是数据库连接串配错修正后重新pm2 restart即可。结合我自己的经验PM2 本身踩坑的几率其实不高大部分问题都出在配置不完整、环境变量不一致、对命令行为理解不准确这几个点上。把这几个关键点理顺PM2 用起来会非常省心线上服务的稳定性和可维护性都会有明显提升。
阅读完成 · 觉得有帮助?
咨询建站