You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PM2集群模式下Express优雅关机事件处理器无法完整执行

问题描述

Express应用实现了优雅关机函数,直接运行时正常工作;但通过PM2以集群模式启动时,函数仅执行第一行console.log('test')就终止,后续清理逻辑未执行。日志中未发现SIGKILL信号,调整PM2的kill timeout、使用no-killtree参数均无效。

PM2日志片段

2022-11-09T15:23:28: PM2 log: App [server:7] starting in -cluster mode-
2022-11-09T15:23:28: PM2 log: App [server:7] online
2022-11-09T15:23:28: PM2 log: App [server:8] starting in -cluster mode-
2022-11-09T15:23:28: PM2 log: App [server:8] online
2022-11-09T15:23:28: PM2 log: App [server:9] starting in -cluster mode-
2022-11-09T15:23:28: PM2 log: App [server:9] online
2022-11-09T15:23:28: PM2 log: App [server:10] starting in -cluster mode-
2022-11-09T15:23:28: PM2 log: App [server:10] online
2022-11-09T15:39:26: PM2 log: Stopping app:server id:7
2022-11-09T15:39:26: PM2 log: Stopping app:server id:8
2022-11-09T15:39:27: PM2 log: App name:server id:8 disconnected
2022-11-09T15:39:27: PM2 log: App [server:8] exited with code [0] via signal [SIGINT]
2022-11-09T15:39:27: PM2 log: App name:server id:7 disconnected
2022-11-09T15:39:27: PM2 log: App [server:7] exited with code [0] via signal [SIGINT]
2022-11-09T15:39:27: PM2 log: pid=50526 msg=process killed
2022-11-09T15:39:27: PM2 log: pid=50533 msg=process killed
2022-11-09T15:39:27: PM2 log: Stopping app:server id:9
2022-11-09T15:39:27: PM2 log: Stopping app:server id:10
2022-11-09T15:39:27: PM2 log: App name:server id:9 disconnected
2022-11-09T15:39:27: PM2 log: App [server:9] exited with code [0] via signal [SIGINT]
2022-11-09T15:39:27: PM2 log: App name:server id:10 disconnected
2022-11-09T15:39:27: PM2 log: App [server:10] exited with code [0] via signal [SIGINT]
2022-11-09T15:39:27: PM2 log: pid=50540 msg=process killed
2022-11-09T15:39:27: PM2 log: pid=50547 msg=process killed

事件处理器代码

const onSignal = (signal) => {
        console.log('test');
        console.log(`${signal} signal received. Cleaning up and shutting down.`);
        console.log('test');

        console.log("Closing HTTPS server to new connections");

        httpsServer.close(error => {
                if (error) {
                        console.log(`Error closing HTTPS server: ${error.message}`);
                        console.log("Process cleanup did not occur due to error.");
                        process.exit(1);
                }
        });

        console.log("Closing SQL Connection Pool");

        db.end().then(() => {
                console.log("SQL Connection Pool successfully ended");
        })
        .catch(reason => {
                console.error(`Failed to close SQL Connection Pool. Error: ${reason.message}`);
        })
        .finally(() => {
                process.exit(0);
        });
};

process.on('SIGINT', onSignal);
process.on('SIGQUIT', onSignal);
process.on('SIGTERM', onSignal);

解决方案

1. 修复异步流程,等待所有清理操作完成

原代码并行执行服务器关闭和数据库关闭,且提前调用process.exit导致清理逻辑未完成就终止。改为先等待服务器关闭所有连接,再关闭数据库,最后退出:

const onSignal = async (signal) => {
  console.log('test');
  console.log(`${signal} signal received. Cleaning up and shutting down.`);
  console.log('test');

  // 强制刷新日志缓冲,避免内容未输出就退出
  process.stdout.flush();

  console.log("Closing HTTPS server to new connections");
  process.stdout.flush();

  // 等待服务器关闭所有现有连接
  await new Promise((resolve, reject) => {
    httpsServer.close(error => {
      if (error) {
        console.log(`Error closing HTTPS server: ${error.message}`);
        console.log("Process cleanup did not occur due to error.");
        reject(error);
      } else {
        console.log("HTTPS server closed successfully");
        resolve();
      }
    });
  });

  console.log("Closing SQL Connection Pool");
  process.stdout.flush();

  // 等待数据库连接池关闭
  try {
    await db.end();
    console.log("SQL Connection Pool successfully ended");
  } catch (reason) {
    console.error(`Failed to close SQL Connection Pool. Error: ${reason.message}`);
  } finally {
    process.exit(0);
  }
};

process.on('SIGINT', onSignal);
process.on('SIGQUIT', onSignal);
process.on('SIGTERM', onSignal);

2. 配置PM2集群模式优雅关机参数

在ecosystem.config.js中添加以下配置,确保PM2等待进程完成清理:

module.exports = {
  apps: [{
    name: 'server',
    script: './app.js',
    instances: 'max',
    exec_mode: 'cluster',
    // 延长PM2等待进程退出的超时时间(单位:毫秒)
    kill_timeout: 60000,
    // 启用就绪通知,让应用启动时告知PM2已就绪
    wait_ready: true,
    // 配置日志输出,避免缓冲
    out_file: './logs/out.log',
    error_file: './logs/error.log',
    merge_logs: true,
    log_date_format: 'YYYY-MM-DD HH:mm:ss'
  }]
};

同时在应用启动成功后添加就绪通知:

httpsServer.listen(port, () => {
  console.log(`Server running on port ${port}`);
  // 告知PM2应用已就绪
  if (process.send) {
    process.send('ready');
  }
});

3. 主动通知PM2关机状态

在信号处理函数开头添加代码,阻止PM2提前终止进程:

const onSignal = async (signal) => {
  // 告知PM2进程正在执行关机清理
  if (process.send) {
    process.send('shutdown');
  }

  // 后续清理逻辑...
};

关键原因分析

  1. 异步流程未正确等待:原代码并行执行清理操作且提前退出,导致逻辑未完成就终止进程。
  2. 日志缓冲问题:Node.js的console.log存在缓冲,进程快速退出时后续日志未及时输出,造成仅执行第一行的假象。
  3. PM2集群IPC机制:PM2依赖IPC连接判断进程状态,若进程未主动通知关机状态,PM2可能提前终止进程。

内容的提问来源于stack exchange,提问作者pdmoerman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 01:55:32