Node.js Process Events 深入全面讲解

发布于:2025-07-17 ⋅ 阅读:(16) ⋅ 点赞:(0)

一、核心事件分类与机制

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)

  • 关键步骤
    1. 停止接收新请求:关闭 HTTP 服务器。
    2. 等待现有请求完成:设置超时(如 30 秒)。
    3. 释放资源:关闭数据库连接、清理定时器。
    4. 退出进程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');
      }
    });
    

四、总结与最佳实践

  1. 错误处理优先级
    • 优先处理 unhandledRejection(Promise 错误)。
    • 同步错误通过 uncaughtException 捕获并退出。
  2. 信号处理流程
    • 监听 SIGINT/SIGTERM 实现优雅退出。
    • 主进程转发信号至子进程(Cluster 模式)。
  3. 资源清理
    • 关闭服务器、数据库连接、定时器。
    • 设置超时避免长时间等待。
  4. 工具与架构
    • 使用 PM2/Docker 管理进程。
    • 多进程架构提升容错能力。

通过合理使用 Node.js 进程事件,可显著提升应用的健壮性和可维护性,实现高可用服务架构。


网站公告

今日签到

点亮在社区的每一天
去签到