在 Node.js 集群模式下,核心目标是避免 worker 进程在异步任务未完成时强制退出。推荐在 worker 进程内实现优雅退出逻辑,监听系统信号并主动关闭服务句柄,适用于有状态连接或重要异步业务的场景。
先说结论:原生 cluster 或 PM2 集群模式下,必须手动处理信号才能实现异步任务完整执行,否则进程会被强制终止。
- 适合:涉及数据库事务、文件写入或长轮询的接口服务
- 先准备:确认进程管理工具(原生 cluster 或 PM2)的信号发送机制
- 验收:重启期间观察日志是否有“连接已关闭”且无报错中断,长耗时请求返回成功
原生 Cluster 与 PM2 信号机制差异
不同管理方式下,worker 接收到的退出信号不同,代码需兼容处理:
- PM2 / Systemd:重启时默认发送
SIGINT或SIGTERM信号。代码监听这两个信号即可。 - 原生 Cluster:主进程调用
worker.disconnect()仅关闭通信通道,不会自动发送SIGTERM。若希望优雅退出,主进程应先发送自定义消息(如{ cmd: 'shutdown' })或手动发送信号(process.kill(pid, 'SIGTERM')),worker 端需监听对应事件。
以下方案以最常见的 PM2 及监听系统信号为例,原生 Cluster 用户需确保主进程配合发送信号。
代码实现方案
核心思路是监听退出信号,停止接收新请求,等待现有请求及异步任务处理完毕后再关闭进程。
const server = require('http').createServer(app);
const db = require('./db'); // 假设的数据库连接
let isShuttingDown = false;
// 统一处理退出信号
process.on('SIGTERM', handleShutdown);
process.on('SIGINT', handleShutdown);
function handleShutdown() {
if (isShuttingDown) return;
isShuttingDown = true;
console.log('收到退出信号,开始优雅关闭...');
// 1. 停止接收新连接
server.close((err) => {
if (err) {
console.error('关闭 HTTP 服务出错', err);
}
});
// 2. 关闭数据库连接等资源
db.close().then(() => {
console.log('数据库连接已关闭');
process.exit(0);
}).catch((err) => {
console.error('关闭数据库出错', err);
process.exit(1);
});
// 3. 设置强制退出超时,防止死锁
setTimeout(() => {
console.error('优雅关闭超时,强制退出');
process.exit(1);
}, 10000);
}配置进程管理器
如果使用 PM2,需在 ecosystem.config.js 中设置 kill_timeout,确保给足代码处理时间,避免 PM2 强制 kill 进程。
module.exports = {
apps: [{
name: 'app',
script: './app.js',
kill_timeout: 15000, // 需大于代码中的 setTimeout 时间
wait_ready: true,
listen_timeout: 10000
}]
};验证方法
1. 日志检查
重启服务时,观察日志是否依次打印“收到退出信号”、“数据库连接已关闭”等信息,且进程退出码为 0。
2. 请求测试
在重启期间发送一个耗时较长的请求。例如创建一个休眠 2 秒的接口,同时执行重启:
# 终端 1:发送长请求
curl -v http://localhost:3000/sleep-2s
# 终端 2:触发重启
pm2 restart app若配置生效,终端 1 应返回 200 状态码;若未生效,请求会失败或挂起。
3. 监控观察
查看错误监控平台,重启时间段内不应出现大量的 502 Bad Gateway 或 ECONNRESET 错误。
常见坑
1. 数据库连接未关闭
只关闭了 HTTP 服务器,没关闭数据库连接池,导致进程无法退出或数据库报连接异常。需在 server.close 回调中或并行关闭 DB 连接。
2. 超时时间设置过短
进程管理器给的等待时间少于业务处理时间,导致代码还没执行完就被强制 kill。需根据业务最长耗时调整 kill_timeout。
3. 忽略未捕获异常
异步任务中抛出未捕获异常会导致 worker 直接退出,绕过信号处理。需监听 process.on('uncaughtException') 记录日志后退出。
4. 原生 Cluster 信号缺失
在原生 cluster 模式下,仅监听 SIGTERM 可能无效,因为 worker.disconnect() 不发送该信号。需确认主进程是否发送了信号或自定义消息。
参考来源
- Node.js Official Documentation, "Cluster", https://nodejs.org/api/cluster.html
- Node.js Official Documentation, "Process Events", https://nodejs.org/api/process.html#event-signals
- PM2 Documentation, "Commands", https://pm2.io/docs/runtime/commands/