使用 pm2 部署 Node.js 应用时如何配置异步任务 graceful shutdown?

文章导读
需要在 Node.js 代码中监听进程信号,配合 PM2 的超时配置,确保异步任务完成后再退出。若配置了 wait_ready,必须在代码中发送 ready 信号。
📋 目录
  1. 命令速用版
  2. 完整代码示例(Express 集成)
  3. 配置说明与风险
  4. 怎么验证是否生效
  5. 常见坑
  6. 参考来源
A A

需要在 Node.js 代码中监听进程信号,配合 PM2 的超时配置,确保异步任务完成后再退出。若配置了 wait_ready,必须在代码中发送 ready 信号。

先说结论:这是一个配置部署类问题,核心在于代码层面的信号监听与 PM2 配置层面的超时调整。

  • 适合:生产环境部署、涉及数据库写入或消息队列消费的场景
  • 先准备:确认应用是否有未完成的异步任务(如 DB 连接、定时器),若使用 wait_ready 需确保代码能发送 ready 信号
  • 验收:执行停止命令后检查日志,确认清理逻辑已执行且无强制杀死记录,异常退出码应为 1

命令速用版

如果你急需止血,可以先调整 PM2 的超时配置,给应用更多清理时间,但这不能替代代码层面的处理。

pm2 stop app_name
pm2 reload app_name

在 ecosystem.config.js 中增加超时配置:

module.exports = {
  apps: [{
    name: 'app',
    script: './app.js',
    kill_timeout: 3000, // 根据任务耗时调整,单位毫秒
    wait_ready: true // 若启用,代码中必须发送 ready 信号
  }]
}

完整代码示例(Express 集成)

以下示例涵盖了服务器初始化、信号监听、Ready 信号发送及异常处理,解决了变量上下文缺失问题,可直接参考集成。

const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;

// 1. 初始化服务器句柄(确保 server 变量可访问)
const server = app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
  // 2. 若 PM2 配置了 wait_ready,需发送 ready 信号
  if (process.send) {
    process.send('ready');
  }
});

// 模拟数据库连接
const dbConnection = {
  close: (cb) => {
    console.log('Closing DB connection...');
    setTimeout(cb, 500);
  }
};

// 3. 统一清理逻辑
function gracefulShutdown(signal) {
  console.log(`收到信号 ${signal},开始清理...`);
  server.close(() => {
    console.log('HTTP 服务器已关闭');
    dbConnection.close(() => {
      console.log('数据库连接已关闭');
      // 正常退出码为 0
      process.exit(0);
    });
  });
  // 防止清理过程卡死,强制超时退出
  setTimeout(() => {
    console.error('清理超时,强制退出');
    process.exit(1);
  }, 5000);
}

// 4. 监听退出信号
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));

// 5. 处理未捕获异常
process.on('uncaughtException', (err) => {
  console.error('未捕获异常', err);
  // 错误状态退出码应为 1,避免误导监控系统
  process.exit(1);
});

配置说明与风险

PM2 在停止或重载应用时,默认会发送 SIGINT 信号。如果 Node.js 进程没有在规定时间内退出,PM2 会强制发送 SIGKILL 杀死进程。默认超时时间较短,如果此时你的应用正在处理数据库事务、写入文件或等待异步回调,强制杀死会导致数据不一致或任务中断。

使用 pm2 部署 Node.js 应用时如何配置异步任务 graceful shutdown?

注意 wait_ready 风险:若在配置中启用了 wait_ready: true,但代码中未在启动完成后发送 process.send('ready'),PM2 会认为应用启动超时并强制重启进程。请确保上述代码中的 ready 信号发送逻辑已启用。

怎么验证是否生效

1. 查看 PM2 日志

执行停止命令后,观察日志输出。应该能看到“收到退出信号”、“HTTP 服务器已关闭”等自定义日志,而不是直接的进程消失。

pm2 logs app_name `--lines` 100

2. 检查强制杀死记录

如果清理时间超过 kill_timeout,PM2 会强制杀死。如果日志中出现类似 timeout 的警告,说明需要增加 kill_timeout 的值。

使用 pm2 部署 Node.js 应用时如何配置异步任务 graceful shutdown?

3. 业务数据验证

在部署重载期间,观察数据库是否有未完成的事务或脏数据。正常 graceful shutdown 不应产生部分写入的数据。

常见坑

1. 定时器未清除

setInterval 或 setTimeout 会保持事件循环活跃,导致进程无法退出。清理时需调用 clearInterval 清除所有定时器。

2. 子进程未处理

使用 pm2 部署 Node.js 应用时如何配置异步任务 graceful shutdown?

如果 Node.js 启动了子进程(如 child_process),主进程退出时子进程可能变成僵尸进程。需要在清理逻辑中手动杀死子进程。

3. Keep-Alive 连接

HTTP 服务器的 keep-alive 连接可能导致 server.close() 等待过久。建议设置合理的 timeout 或强制关闭空闲连接。

4. 集群模式下的信号

PM2 集群模式下,信号会发送给每个 worker。确保每个 worker 都能独立处理清理逻辑,不要依赖主进程协调。

参考来源

  • PM2 Official Documentation - Process Management: Graceful Shutdown
  • Node.js Documentation - Process Events: Signal Events