← 中文资源

简体中文版本

PM2 — max_memory_restart 引发重启循环

排查 PM2 中 RSS 触碰 max_memory_restart 导致的重启循环,区分泄漏与基线增长,并按真实内存画像设定阈值。

阅读英文原文

症状

  PM2 在自家遥测里留下清晰指纹:restarts 数以百计、uptime 永远只有几分钟、日志里反复出现 restart permission denied by memory limit。决定性比较是稳态 RSS 与配置阈值:一个诚实工作集 400 MB 的应用配 300 MB 上限会永远循环却毫无疾病——按配置杀是对的,按负载看配置是错的。每次重启都在丢在途请求,负载均衡日志里出现周期性 502 窗口。

诊断思路

  先看重启模式:固定内存阈值、规律间隔是限额触发;崩溃循环会带错误堆栈,内存循环是干净退出。再用 pm2 monit 观察完整流量周期的 RSS 形状,稳态若已贴着上限,错的是阈值不是应用。最后区分泄漏与基线:按分钟记录 memoryUsage,暖机后平坦说明是容量问题;无平台期的单调增长说明有代码级泄漏要修。注意 max_memory_restart 管的是整个进程 RSS,含原生插件分配,–max-old-space-size 管不到的那部分。

根因

  RSS 越过 max_memory_restart 时 PM2 杀死进程并重启。阈值低于负载合法稳态足迹,或泄漏让每次重启从已升高的基线起步,进程就会无限循环杀-重启,每个周期都丢弃在途请求。

修复与验证

  先分类再动手。RSS 平坦就按“实测峰值 + 30% 余量”重设阈值;单调增长就修代码(逐请求事件监听器、并非真 LRU 的缓存、transform 里累积的缓冲)。数值要保持连贯顺序:–max-old-space-size 管 V8 堆且必须小于 max_memory_restart,让 GC 先于 PM2 有机会出手,两者都从实测峰值推导。验证时跑含真实峰值的完整流量周期:重启数为零、RSS 平台期明显低于上限、均衡器日志无 502 窗口;再到预发环境注入人为泄漏,确认 PM2 仍会在威胁宿主前重启它——机制必须保持在岗、尺寸正确,并在框架支持时配置优雅重启以排空在途请求。绝不要直接删掉 max_memory_restart:那是防泄漏的最后防线,拆掉等于把决定权交给 OOM killer。

代码示例

$ pm2 describe app
│ status         │ online              │
│ restarts       │ 217                  │
│ uptime         │ 4m                   │
// log the shape on an interval
setInterval(() => {
  const m = process.memoryUsage();
  console.log({ rss: MB(m.rss), heap: MB(m.heapUsed), ext: MB(m.external) });
}, 60_000);
{
  "apps": [{
    "name": "app",
    "script": "server.js",
    "max_memory_restart": "600M",
    "node_args": "--max-old-space-size=450"
  }]
}