pm2 进程管理实战:Node 服务状态、重启与日志全解析
2026/9/17 5:56:01 网站建设 项目流程

1. 为什么我把 Node 服务从 nohup 挪到了 pm2

我第一次把 Node 服务扔到服务器上,用的是最土的办法:nohup node app.js > app.log 2>&1 &。当天晚上跑得挺好,第二天早上登录一看,进程没了,日志最后一行停在凌晨三点多,没有任何报错。后来才知道是内存吃满被系统清掉了。那之后我陆续试过写 systemd 单元文件、写 shell 守护脚本,直到换成 pm2,才算真正把「查看服务状态、重启服务、看日志」这三件每天都在做的事固定成了一套顺手流程。

这篇内容就是围绕这三件事展开的:怎么用 pm2 快速看清现在有几个进程、各自什么状态;怎么在不停服或者少停服的前提下重启;日志到底写在哪、怎么看、怎么防止它把磁盘写满。整套东西适合已经在跑 Node 服务但还在用nohup+ps+tail -f手动折腾的人,也适合刚接手一台别人配置过的服务器、需要快速搞清楚现状的人。命令不复杂,真正费时间的是那些「重启完发现没生效」「日志文件把盘吃满了」「开了 cluster 结果内存翻倍」的坑,我会把我踩过的都写出来。

需要先说清楚一点:pm2 是一个进程管理器,它本身不解决业务代码的问题。进程反复重启,根因多半在代码或环境,pm2 只是把这件事暴露得更明显。想清楚这个前提,后面的排查思路才不会跑偏。

2. 查看服务状态:先把家底盘清楚

接手一台服务器,我的第一条命令永远是pm2 list,而不是先去看代码。原因很简单:代码可以慢慢读,但进程现在是不是活着、重启了多少次、吃了多少内存,这些是会变的,先固化下来一份现场快照,后面所有判断才有依据。

2.1 pm2 list 以及那几个等价的写法

pm2 listpm2 lspm2 statuspm2 ps这四个命令输出完全一样,纯粹是照顾不同人的肌肉记忆。我自己习惯敲pm2 ls,因为短。输出大致是这样一张表:

pm2 ls
┌────┬──────────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐ │ id │ name │ namespace │ version │ mode │ pid │ uptime │ │ status │ cpu │ mem │ user │ watching │ ├────┼──────────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼────────────────────┼──────────┼──────────┤ │ 0 │ api-gateway │ default │ 1.4.2 │ cluster │ 18231 │ 3D │ 2 │ online │ 0.3% │ 118.4mb │ deploy │ disabled │ │ 1 │ worker │ default │ 1.4.2 │ fork │ 18302 │ 3D │ 18 │ online │ 1.1% │ 240.7mb │ deploy │ disabled │ └────┴──────────────┴──────────────────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘

这张表里我最常盯的是三列。第一列是,也就是 restart 次数,这是判断服务健康度最直观的指标:刚部署完是 0,跑了三天还是 0,说明稳;如果一小时内涨到几十,那基本可以判定进程在反复崩溃,此时不要急着重启,先去翻日志。第二列是uptime,如果某个进程的 uptime 明显比其他实例短,说明它刚刚重启过,可能是被内存阈值干掉,也可能是被手动重启。第三列是mem,Node 应用的常驻内存在 100 到 300MB 之间算正常区间,如果某个实例涨到 1.5GB 还在一路往上爬,那基本就是内存泄漏的早期信号。

提示:pm2 ls的输出在终端宽度不够时会被截断,这时候加--no-color或者把终端拉宽,比反复滚动屏幕省事得多。

2.2 pm2 describe:看单个进程的全部家底

列表只给概览,要看细节得用pm2 describe,或者它的同义词pm2 show

pm2 describe api-gateway # 也可以用 id pm2 show 0

它的输出信息量很大,我一般重点看这几块:script path确认跑的是哪个文件(服务器上有多个部署目录时,这一步能救命,避免你在错误的目录里改了半小时代码却毫无效果);exec mode确认是 fork 还是 cluster,这个直接决定了后面reload能不能做到平滑;node argsargs确认启动参数有没有带上;error log pathout log path直接告诉你日志文件在哪,省得去猜路径;restarts和历史重启时间点则能帮你判断崩溃是持续性的还是偶发的。

还有一个细节值得说:pm2 describe会显示unstable restartscreated at。如果unstable restarts不为 0,说明进程在启动后极短时间内就挂了,被 pm2 判定为「不稳定重启」。这种情况光看主日志往往没用,得去 error 日志里找启动阶段的报错,常见的是端口占用、环境变量缺失、依赖没装全。

2.3 pm2 monit:给不懂命令的人看的实时面板

如果同事不熟悉命令行,或者我在排查 CPU 尖刺的时候想直观看到内存曲线,我会开pm2 monit

pm2 monit

它是一个全屏的交互式面板,左边是进程列表,右边分为上下两块:上面是选中的进程的实时日志流,下面是 CPU 和内存的实时数值。按上下方向键可以切换进程,Ctrl+C 退出。

这个面板有个很大的优势:它把「日志」和「资源占用」放在同一个屏幕里。很多性能问题的线索就藏在两者的时间关系里——比如你看到日志里每隔 30 秒出现一次批量任务开始,同时内存台阶式上跳,那基本可以确定是定时任务在处理大批数据时没有释放中间对象。

不过它不适合长时间挂着,因为它是实时渲染,通过 SSH 连接时会持续占用带宽。我要长期观察某个指标的时候,更倾向于用pm2 describe加上系统自带的top或者监控面板,而不是把 monit 开着不管。

2.4 查看命令的横向对比

命令输出形态适合场景是否会持续刷新
pm2 ls表格概览快速确认进程数量与状态否,一次输出
pm2 describe <name>详细键值对排查配置、路径、重启历史
pm2 monit全屏实时面板边看日志边看资源占用是,直到手动退出
pm2 jlistJSON交给脚本解析、接入自建监控
pm2 prettylist格式化 JSON人工阅读 JSON 字段

pm2 jlist这个命令值得单独提一句。它输出的是机器可读的 JSON,内容比pm2 ls的表丰富得多,包括每个进程的环境变量、内存上限、重启延迟等。我早期自己写过一个每分钟执行一次的采集脚本,就是把pm2 jlist的输出解析后写进时序库,这样就能画出过去一周的重启次数曲线。很多团队上来就上完整的监控体系,其实在规模不大的时候,用pm2 jlist加一个定时任务就能解决大部分「什么时候开始变坏的」问题。

3. 重启服务的几种姿势和它们的区别

「重启」这个词在 pm2 里对应着好几个不同的命令,它们的语义差别很大,用错了要么白重启,要么短暂中断服务。我见过最常见的误用,是在 cluster 模式下用restart去更新代码,结果所有实例同时挂掉又同时起来,接口出现几秒钟的 502。

3.1 restart、reload、stop 加 start 到底差在哪

先说结论:restart是杀死进程再拉起来,reload是平滑重启(仅在 cluster 模式下有意义),stopstart等于彻底停止再启动,多了一步状态清理。

pm2 restart的行为是直接向进程发送信号,默认是SIGINT,进程收到后被终止,然后 pm2 立刻用同样的配置重新拉起。这个过程有个明显特征:进程 ID 会变,uptime归零,计数加一。它快,但在这几十毫秒到几秒的窗口里,进程是不处理请求的。如果是单实例部署,用户就会遇到一瞬间的连接失败。

pm2 reload则完全是另一套逻辑。它只在 cluster 模式下有效,做的事情是逐个替换 worker:先启动一个新的 worker,等它报告 ready(默认通过 Node 的process.send('ready')或者监听端口成功来判断),再把老的 worker 优雅关闭。整个过程对外始终有实例在监听端口,所以能做到用户无感知。代价是内存会短暂翻倍,因为新旧 worker 会有一段时间共存。

pm2 stoppm2 startrestart的区别在于:stop会把进程状态标记为stopped,你可以在停止期间修改配置、调整环境变量,然后再启动。有些场景下必须走这条路,比如要修改exec_mode、实例数量或者启动脚本本身,这些改动restart是不认的——restart只复用已有配置,改instances这类参数后必须delete再加start才会生效。

# 强制杀死再拉起,最快,但有短暂中断 pm2 restart api-gateway # 平滑重启,cluster 模式下用户无感知 pm2 reload api-gateway # 停止后修改配置再启动 pm2 stop api-gateway # ...调整 ecosystem 配置... pm2 start api-gateway

3.2 精准重启单个进程与批量操作

pm2 支持通过三种标识来指定目标:进程名、进程 id、以及all

pm2 restart api-gateway # 按名字 pm2 restart 0 # 按 id pm2 restart all # 全部 pm2 restart "api-*" # 按名字前缀匹配

带引号的通配符匹配这个用法在实际运维里非常实用。比如我把网关相关的几个服务统一命名成gateway-httpgateway-wsgateway-admin,那更新网关层代码后只需要一条pm2 restart "gateway-*",不用担心漏掉某个或者误伤业务服务。命名规范这件事在只有两三个服务时看不出价值,等到服务超过十个,你会庆幸当初统一了前缀。

还有一种不常见的操作:只重启某个进程组里的部分实例。pm2 本身没有直接支持,但可以借助cluster模式下实例的独立 id 来实现,先pm2 ls找到具体实例 id,再pm2 restart <id>。这个技巧在做灰度验证时有用——比如你想先让一个新版本只在一个实例上跑五分钟看看错误率,确认无异常后再 reload 全部。

3.3 重启后没生效?这三个坑我都踩过

第一个坑是环境变量。pm2 在启动进程时会继承当时的 shell 环境变量,并在restart时复用这份快照。也就是说,如果你在.bashrc里加了一个新的环境变量,然后执行pm2 restart,进程里是看不到这个新变量的。必须pm2 delete然后重新pm2 start,才会重新读取。我因为这个排查了整整一个下午,一直以为是代码里的配置读取逻辑有问题。

第二个坑是代码没有真正更新。pm2 restart只是重启进程,不会拉取新代码,也不会重新执行构建。如果你的部署流程是「git pull 然后 pm2 restart」,那对于需要编译的项目(TypeScript 编译产物、前端构建产物)是不够的,编译产物还是旧的。正确顺序是先构建再重启,而且要注意构建产物落在的目录和describe里显示的script path必须一致。

第三个坑是reload在 fork 模式下等于restart。很多人以为pm2 reload永远是平滑的,其实不是。如果进程是以 fork 模式启动的,pm2 没有多实例可供轮流切换,reload会退化成和restart一样的硬重启。所以想享受平滑重启,前提是启动时就用了 cluster 模式,也就是-i参数指定了实例数量(-i max表示按 CPU 核心数)。

注意:reload的平滑效果依赖进程能正确报告 ready。如果你的应用启动后需要几秒钟初始化数据库连接池,但在启动瞬间就开始监听端口,那 pm2 会认为它已经就绪并关闭旧实例,此时新实例其实还不能正常处理请求,用户依然会碰到错误。这种场景下需要在代码里显式调用process.send('ready'),并配合wait_ready: truelisten_timeout使用。

4. 日志:pm2 logs 只是入门

日志是排查问题唯一可靠的线索来源,但很多人对 pm2 日志的认知停留在「敲pm2 logs看滚动输出」。真到了要定位一个三天前发生的偶发错误时,滚动输出帮不上任何忙,你得知道日志文件的准确位置、两个文件的区别、以及怎么在几百万行里快速筛出目标。

4.1 pm2 logs 的常用参数组合

pm2 logs默认会持续流式输出所有进程的 stdout 和 stderr,并且会在前面加上进程名和 id 作为前缀。日常我常用的组合有这么几个:

# 只看某个进程,滚动输出 pm2 logs api-gateway # 只看错误输出 pm2 logs api-gateway --err # 只看标准输出 pm2 logs api-gateway --out # 一次性打印最后 500 行然后退出,不挂住终端 pm2 logs api-gateway --lines 500 --nostream # 输出纯文本,去掉 pm2 自己加的前缀,方便重定向到文件 pm2 logs api-gateway --raw --lines 2000 > /tmp/dump.log # 输出 JSON,交给脚本处理 pm2 logs --json --lines 1000 --nostream

--nostream这个参数我要重点强调。默认的pm2 logs是流式的,会一直挂在那里,很多人通过脚本调用它时会发现脚本永远不返回,就是这个原因。加上--nostream后它变成一次性的输出命令,可以直接放进脚本里做日志快照。

--raw也很有价值。默认输出里每一行都会带上进程名 |这样的前缀,还有 pm2 自己加的换行处理。当你需要把日志丢给grep做精确匹配、或者丢给日志分析工具时,这些前缀会干扰结果。--raw会把原始内容原样吐出来。

4.2 日志文件在哪,为什么会有两个文件

日志的默认目录是当前用户的~/.pm2/logs/,文件名规则是<进程名>-out.log<进程名>-error.log。所以api-gateway这个进程的日志就是:

~/.pm2/logs/api-gateway-out.log ~/.pm2/logs/api-gateway-error.log

不记得路径的时候,pm2 describe api-gateway里的out log patherror log path字段会直接告诉你,这比去猜路径靠谱。

为什么是两个文件?这跟 Unix 的标准流设计有关。进程有 stdout(标准输出)和 stderr(标准错误)两个独立的流,console.log走前者,console.error走后者。pm2 默认把它们分别重定向到两个不同的文件里。这个设计有好处也有坏处:好处是你可以单独--err只看错误,噪音少;坏处是当一个请求的错误链路同时打了普通日志和错误日志时,两边的顺序对不上,排查时需要来回对照。

我的做法是在配置文件里加上merge_logs: true,把两个流合并到一个文件里。对于 cluster 模式下的多实例,合并日志尤其重要,否则不同实例的日志散落在不同文件里,你根本拼不出一个请求的完整生命周期。合并之后所有实例的输出都进同一个文件,靠行首的进程 id 前缀区分来源。

4.3 用 pm2 flush 和 pm2-logrotate 管住磁盘

日志这块最典型的故障不是「看不到日志」,而是「日志把磁盘写满了」。Node 服务的日志增长速度经常被低估,一个每秒处理几百请求的接口,如果每个请求打三行日志,一天下来轻松上百 GB。磁盘一满,数据库写不进去、进程起不来,整个服务链崩掉。

第一个要会用的命令是pm2 flush

# 清空所有进程的日志文件 pm2 flush # 只清空某个进程 pm2 flush api-gateway

它的作用是把日志文件内容截断为 0,不会删除文件本身。这个操作要谨慎,清掉之后就真没了,所以我的习惯是先pm2 logs --nostream --lines 5000 > /tmp/backup-$(date +%F).log备份一份,再 flush。

真正解决问题的是日志切割。pm2 自身不带轮转功能,需要装一个官方模块:

pm2 install pm2-logrotate

装完之后它的默认行为是:单文件超过 10MB 就切割、保留 30 份、开启压缩。这几个默认值对生产环境来说都偏保守,我一般会改成这样:

pm2 set pm2-logrotate:max_size 100M pm2 set pm2-logrotate:retain 14 pm2 set pm2-logrotate:compress true pm2 set pm2-logrotate:rotateInterval '0 0 * * *' pm2 set pm2-logrotate:dateFormat YYYY-MM-DD_HH-mm-ss pm2 set pm2-logrotate:workerInterval 30

参数含义逐个说清楚。max_size是单个日志文件的大小上限,超过就切;retain是保留的历史文件份数,14 份配合每日轮转就是两周的可追溯窗口,这个长度对大多数团队够用了,毕竟再久之前的日志一般也查不到有效信息;compress开启 gzip 压缩,能把文本日志压到原来的十分之一左右;rotateInterval是按固定时间切割,我设成每天零点,这样日志文件天然按天分段,排查时直接按日期找文件;workerInterval是检查频率,单位秒,30 秒意味着最坏情况下日志会超出上限一点,但对磁盘影响可以忽略。

注意:改完 pm2-logrotate 的配置后,别忘了执行一次pm2 reloadLogs。这个命令是让 pm2 重新打开日志文件句柄。切割工具做的是重命名或删除文件,但 pm2 持有的还是旧的文件描述符,如果不 reload,进程会继续往那个已经被移走的文件里写,导致「切割生效了但新文件没内容」的诡异现象。

5. 开机自启、集群模式和 ecosystem 配置

前面讲的都是运行时操作,这一节讲怎么把配置固化下来。手工pm2 start出来的进程,一旦服务器重启就全没了,而且各种参数散落在命令行里,过两个月自己都想不起来当时为什么加了某个参数。

5.1 startup 和 save 的正确操作顺序

开机自启依赖两步,而且顺序不能反。

# 第一步:生成并安装系统服务单元 pm2 startup # 它会输出一条类似下面的命令,需要你手动执行(提示会带具体用户和路径) # sudo env PATH=$PATH:/usr/bin pm2 startup systemd -u deploy --hp /home/deploy # 第二步:把当前进程列表存成快照 pm2 save

pm2 startup做的是在你的系统里注册一个开机启动项,让机器启动时自动拉起 pm2 守护进程。它本身不会记住你有哪些应用,所以必须再执行pm2 save,把当前内存里的进程列表写到~/.pm2/dump.pm2这个快照文件里。开机时 pm2 读这个文件来恢复所有进程。

我踩过的坑是这样:先执行了pm2 save,之后又加了一个新服务,但忘记再save一次,结果服务器重启后新服务没起来,排查了半天才发现快照是旧的。所以我的习惯是——只要动过进程列表(新增、删除、改了名字或启动参数),马上补一条pm2 save

还有一点,pm2 startup输出的那条命令必须用提示里的完整形式执行,尤其是-u--hp参数。如果直接sudo pm2 startup而不带用户信息,开机启动会以 root 身份运行 pm2,你的应用也跟着以 root 跑,~/.pm2/logs目录会变成 root 所有,之后用普通用户操作时会到处都是权限报错。

恢复快照也可以手动触发:

pm2 resurrect

这个命令在误删进程列表之后特别有用,能从快照文件里把进程定义重新读出来。

5.2 cluster 模式与实例数的选择

Node 是单线程执行 JavaScript 的,一个进程只能用到一个 CPU 核心。现代服务器动辄 8 核 16 核,单进程部署等于浪费了绝大部分算力。cluster 模式解决的就是这个问题,它基于 Node 内置的 cluster 模块,fork 出多个共享同一个监听端口的 worker。

# 按 CPU 核心数自动决定实例数 pm2 start app.js -i max # 指定 4 个实例 pm2 start app.js -i 4 # 在配置文件中写死 # instances: 4

实例数怎么定?max是常见起点,但它有个前提:你的应用必须是「无状态」或者「状态外置」的。如果应用在内存里维护会话、缓存、定时任务,多实例会立刻出问题——用户请求可能落到不同实例上,会话丢失;定时任务每个实例都跑一遍,数据被重复处理。这种情况下要么先用一个实例,要么把这些状态挪到外部存储里。

另一个需要提醒的点是内存。cluster 模式下内存是按实例累加的,pm2 ls里显示的 mem 是单个实例的值。设了max_memory_restart: 500M,8 个实例的实际内存上限是 4GB 而不是 500MB。我曾经在 4GB 内存的机器上开了 8 个实例,每个限 800M,结果触发系统 OOM,进程被系统杀掉,pm2 又自动重启,形成循环。正确的算法是先算总可用内存,减去系统和数据库占用,再除以实例数,留出 30% 的余量。

5.3 一份可以直接抄的 ecosystem.config.js

配置文件的优势是版本可控、参数集中、多环境切换清晰。我把它放在项目根目录,和代码一起提交到仓库。

module.exports = { apps: [ { name: 'api-gateway', script: './dist/server.js', cwd: '/opt/apps/api-gateway', instances: 4, exec_mode: 'cluster', watch: false, max_memory_restart: '600M', autorestart: true, max_restarts: 10, min_uptime: '20s', restart_delay: 3000, kill_timeout: 8000, wait_ready: true, listen_timeout: 15000, merge_logs: true, log_date_format: 'YYYY-MM-DD HH:mm:ss.SSS', error_file: '/data/logs/api-gateway/error.log', out_file: '/data/logs/api-gateway/out.log', env: { NODE_ENV: 'production', PORT: 3000 }, env_staging: { NODE_ENV: 'staging', PORT: 3100 } } ] }

挑几个参数解释为什么这么设。max_restarts配合min_uptime是一道保险:如果进程在启动后 20 秒内就崩溃,且这种情况连续发生 10 次,pm2 会停止重启并把它标记为errored。这比无限重启要好得多,因为无限重启会让日志被刷屏,真正的第一条报错被淹没,同时也消耗大量 CPU。restart_delay是每次重启前的等待时间,给外部依赖(数据库、缓存)一点恢复时间,避免雪崩式的重启风暴。kill_timeout是给进程的优雅退出时限,如果你的应用在退出前需要关闭连接池、写完缓冲日志,这个值要设得比默认的 1600ms 更大一些。

启动和切换环境:

pm2 start ecosystem.config.js pm2 start ecosystem.config.js --env staging pm2 reload ecosystem.config.js --env production

用配置文件启动后,pm2 reload ecosystem.config.js可以一次性对配置里所有应用做平滑重启,比逐个敲命令省事很多。

6. 常见故障排查实录

这一节是我这几年的问题记录本,按现象反查原因。真出事的时候,顺着「现象 → 判断依据 → 处置方式」去看比从头读文档快得多。

6.1 进程反复重启,restart 次数一路飙升

最典型的表现就是pm2 ls里某个进程的数字不停增长,状态在onlineerrored之间跳。我的排查顺序固定是三步。

第一步,看 error 日志的末尾。因为重启循环会把日志刷得很快,所以要立刻抓一份快照:

pm2 logs api-gateway --err --lines 300 --nostream

第二步,看最新一次重启前的完整启动过程。如果应用启动阶段就有语法错误、模块加载失败、端口占用,这里会直接暴露:

pm2 describe api-gateway | grep -A 5 "restart time"

第三步,判断是「启动即崩」还是「跑一阵才崩」。pm2 describe里的unstable restarts如果不为 0,说明是启动阶段就挂,问题在代码或环境;如果这个值是 0,但重启次数在涨,说明是运行一段时间后崩,方向转向内存、未捕获异常、或者外部依赖超时。

按我的经验,重启循环的原因分布大概是这样的:端口被占用(EADDRINUSE)占一成多,环境变量缺失占两成,未捕获的 Promise 异常占三成,内存超限占两成多,剩下的是外部依赖连不上。端口占用这个尤其常见于「旧进程没杀干净」的场景,此时pm2 delete再加startrestart更彻底。

6.2 日志不输出,或者输出乱码

日志完全不写的第一个检查点,是看进程有没有真正在运行。pm2 ls显示online但日志为空,通常是应用本身没打日志,或者日志级别设成了 warn 以上。用pm2 logs --raw直接看原始输出能排除 pm2 前缀的干扰。

第二个检查点是日志文件的权限和目录。如果error_file指向的目录不存在,pm2 启动时会报警。自定义日志路径时一定要确认目录已经创建,并且当前用户有写权限:

mkdir -p /data/logs/api-gateway chown deploy:deploy /data/logs/api-gateway

至于乱码,多数情况是日志内容里混了彩色控制字符(比如某些库在检测到终端时输出 ANSI 转义码)。解决办法是在生产环境里关掉彩色输出,或者用--rawsed过滤掉转义序列。我在 CI 环境里统一设了FORCE_COLOR=0,从此再没遇到过。

6.3 内存缓慢上涨与 max_memory_restart 的正确用法

max_memory_restart经常被当成万能药,其实它只是一个兜底。它的机制是:pm2 定期检查进程的内存占用,一旦超过阈值就把它重启。问题是这个检查有间隔,而且重启本身是有代价的——正在处理的请求会被打断,用户会收到错误。

所以更正确的姿势是把它设成一个「明显异常」的值,而不是「稍微偏高就要重启」的值。判断依据可以从历史数据来:正常运行时内存稳定在 200MB,偶发峰值到 400MB,那阈值设 800MB 比较合理,只有真的失控了才触发。

内存持续上涨的排查,我一般会先确认是不是缓存没设上界。很多库默认是无限制缓存,比如某些 ORM 的查询缓存、某些 HTTP 客户端的连接池。给它们都设上明确的上限之后,内存曲线通常就会从斜线变成平线。这一步不解决的话,重启只是把问题推迟几十分钟。

6.4 常见问题速查表

现象大概率原因处置方式
启动即崩,unstable restarts大于 0端口占用、环境变量缺失、依赖未安装查 error 日志首屏,pm2 delete后重启
重启次数持续增长,运行一阵才崩未捕获异常、内存超限、外部依赖超时抓 error 日志末段,结合内存曲线判断
reload后仍有短暂 502实际是 fork 模式,或未实现 ready 上报改 cluster 模式,代码中调用process.send('ready')
改了环境变量但restart后没生效pm2 复用启动时的环境快照pm2 delete后重新start
磁盘被日志写满未配置日志轮转安装 pm2-logrotate 并调大 retain
日志切割后新文件为空pm2 未重新打开文件句柄执行pm2 reloadLogs
服务器重启后服务不全pm2 save快照过期重新pm2 save,必要时pm2 resurrect
多实例下会话丢失应用持有内存态会话会话外置到共享存储,或先退回单实例

这张表我建议直接存成便利贴,因为绝大多数线上告警都能对到其中一行。剩下那些对不上的,基本就是业务代码自己的问题了,方向不同,排查手段也不同。

7. 一些不值得重复踩的经验

关于 pm2,我最后想分享几个和命令无关、但确实省了我很多时间的做法。

其一是给进程起名字要克制。我见过用完整路径当名字的、用中文当名字的、用时间戳当名字的,最后都因为名字太长或者不好敲而后悔。统一用「业务域-组件」的短横线格式,长度控制在二十个字符内,日常操作会舒服非常多。

其二是把pm2 save绑进部署脚本。我现在的部署脚本末尾固定有一句pm2 reload ecosystem.config.js && pm2 save,这样快照永远和实际运行状态一致,服务器意外重启时不会出现版本错乱。

其三,日志目录别放在系统盘。日志的写入量大且不可控,一旦撑爆系统盘,连登录和排查都成问题。把日志路径指到独立的数据盘上,即使那块盘写满,也只是服务不可用,机器还能进去处理。这个建议听起来很基础,但我在生产环境里见过不止一次因为日志写满根分区导致整台机器不可用的案例。

其四,遇到「重启就好了」的故障,一定不要止步于重启。重启只是把现场清掉了,问题还在。我的习惯是重启前先执行一次pm2 logs --nostream --lines 5000 > /tmp/incident-$(date +%s).log,把现场保留下来,等恢复之后再慢慢分析。这个动作只花两秒钟,但经常是唯一能找到根因的机会。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询