需要在 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 杀死进程。默认超时时间较短,如果此时你的应用正在处理数据库事务、写入文件或等待异步回调,强制杀死会导致数据不一致或任务中断。
注意 wait_ready 风险:若在配置中启用了 wait_ready: true,但代码中未在启动完成后发送 process.send('ready'),PM2 会认为应用启动超时并强制重启进程。请确保上述代码中的 ready 信号发送逻辑已启用。
怎么验证是否生效
1. 查看 PM2 日志
执行停止命令后,观察日志输出。应该能看到“收到退出信号”、“HTTP 服务器已关闭”等自定义日志,而不是直接的进程消失。
pm2 logs app_name `--lines` 1002. 检查强制杀死记录
如果清理时间超过 kill_timeout,PM2 会强制杀死。如果日志中出现类似 timeout 的警告,说明需要增加 kill_timeout 的值。
3. 业务数据验证
在部署重载期间,观察数据库是否有未完成的事务或脏数据。正常 graceful shutdown 不应产生部分写入的数据。
常见坑
1. 定时器未清除
setInterval 或 setTimeout 会保持事件循环活跃,导致进程无法退出。清理时需调用 clearInterval 清除所有定时器。
2. 子进程未处理
如果 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