一、核心事件分类与机制
1. 生命周期事件
(1) beforeExit
事件
- 触发条件:当 Node.js 事件循环数组为空且没有额外工作被添加时触发。
- 特点:
- 允许执行异步操作(如关闭数据库连接、清理资源)。
- 不会触发的情况:
- 显式调用
process.exit()
。 - 发生未捕获异常(
uncaughtException
)。
- 显式调用
- 代码示例:
process.on('beforeExit', (code) => { console.log(`Process will exit with code: ${code}`); // 可执行异步操作,如: // server.close(() => process.exit(code)); });
(2) exit
事件
- 触发条件:显式调用
process.exit()
或进程正常结束时触发。 - 特点:
- 不允许异步操作,事件循环已停止。
- 适合执行同步清理(如写入日志)。
- 代码示例:
process.on('exit', (code) => { console.log(`Exiting with code: ${code}`); // 仅同步操作,如: // fs.writeFileSync('./exit.log', 'Process exited'); });
2. 信号事件(Signal Events)
常见信号处理
SIGINT
(Ctrl+C):用户中断进程。SIGTERM
:优雅终止请求(如容器停止、PM2 重启)。SIGHUP
:终端断开或配置变更。- 代码示例:
process.on('SIGINT', () => { console.log('Received SIGINT. Shutting down gracefully...'); server.close(() => { process.exit(0); }); });
3. 错误事件
(1) uncaughtException
- 触发条件:未捕获的同步错误。
- 最佳实践:
- 避免恢复进程:官方建议捕获后立即退出。
- 记录错误日志并释放资源。
- 代码示例:
process.on('uncaughtException', (err) => { console.error('Uncaught Exception:', err); logger.error(err.stack); server.close(() => { process.exit(1); }); });
(2) unhandledRejection
- 触发条件:未处理的 Promise 拒绝(Node.js 14+ 默认导致进程崩溃)。
- 最佳实践:
- 统一捕获并转换为错误日志。
- 结合
uncaughtException
处理。
- 代码示例:
process.on('unhandledRejection', (reason, promise) => { console.error('Unhandled Rejection:', reason); logger.error({ reason, promise }); // 可选择退出进程 process.exit(1); });
4. 警告事件(warning
)
- 触发条件:Node.js 发出警告(如内存泄漏、实验性功能使用)。
- 代码示例:
process.on('warning', (warning) => { console.warn('Process Warning:', warning.name); console.warn(warning.stack); });
二、高级主题与最佳实践
1. 优雅退出(Graceful Shutdown)
- 关键步骤:
- 停止接收新请求:关闭 HTTP 服务器。
- 等待现有请求完成:设置超时(如 30 秒)。
- 释放资源:关闭数据库连接、清理定时器。
- 退出进程:
process.exit(0)
。
- 代码示例(Express + Cluster):
const server = app.listen(port, () => { console.log(`Server running on port ${port}`); }); process.on('SIGTERM', () => { console.log('SIGTERM received. Shutting down...'); server.close(async () => { await db.disconnect(); clearTimeout(timeoutId); process.exit(0); }); });
2. 多进程架构(Cluster)
- 使用
cluster
模块:- 主进程监听信号并转发给子进程。
- 子进程独立处理错误,避免全站崩溃。
- 代码示例:
const cluster = require('cluster'); const numCPUs = require('os').cpus().length; if (cluster.isMaster) { for (let i = 0; i < numCPUs; i++) { cluster.fork(); } cluster.on('exit', (worker, code, signal) => { console.log(`Worker ${worker.process.pid} died. Restarting...`); cluster.fork(); }); } else { // 子进程代码(同单进程逻辑) process.on('SIGTERM', () => { server.close(() => process.exit(0)); }); }
3. 进程管理工具
- PM2:
- 自动重启:
pm2 start app.js --watch
- 零秒停机:
pm2 start app.js --kill-timeout 5000
- 自动重启:
- Docker:
- 使用
--restart=always
策略。 - 配合
healthcheck
指令监控状态。
- 使用
4. 错误处理库推荐
graceful-process
:const graceful = require('graceful-process'); graceful({ onError: (err) => { console.error('Error:', err); logger.error(err); } });
三、常见陷阱与解决方案
1. 陷阱:uncaughtException
后继续运行
- 问题:捕获后不退出进程可能导致内存泄漏或状态不一致。
- 解决方案:
process.on('uncaughtException', (err) => { logger.fatal(err); process.exit(1); // 强制退出 });
2. 陷阱:未处理 Promise 拒绝
- 问题:Node.js 14+ 默认崩溃。
- 解决方案:
process.on('unhandledRejection', (reason) => { logger.error('Unhandled Rejection:', reason); process.exit(1); });
3. 陷阱:信号事件未转发至子进程
- 问题:Cluster 模式下子进程未响应 SIGTERM。
- 解决方案:
// 主进程 cluster.on('message', (worker, msg) => { if (msg.type === 'shutdown') { worker.kill('SIGTERM'); } });
四、总结与最佳实践
- 错误处理优先级:
- 优先处理
unhandledRejection
(Promise 错误)。 - 同步错误通过
uncaughtException
捕获并退出。
- 优先处理
- 信号处理流程:
- 监听
SIGINT
/SIGTERM
实现优雅退出。 - 主进程转发信号至子进程(Cluster 模式)。
- 监听
- 资源清理:
- 关闭服务器、数据库连接、定时器。
- 设置超时避免长时间等待。
- 工具与架构:
- 使用 PM2/Docker 管理进程。
- 多进程架构提升容错能力。
通过合理使用 Node.js 进程事件,可显著提升应用的健壮性和可维护性,实现高可用服务架构。