Node.js(六)进程与子进程深入详解

一、Node.js 进程概述

  • 进程(Process):操作系统中正在运行的程序实例。每个 Node.js 应用启动后就是一个进程。
  • Node.js 是单线程的,但可以通过子进程实现多进程并发,提升性能或处理 CPU 密集型任务。

二、进程相关的核心模块

1. process 模块

Node.js 全局对象,代表当前运行的进程。

常用属性和方法:

  • process.pid:当前进程 ID
  • process.argv:启动参数数组
  • process.env:环境变量对象
  • process.exit([code]):退出进程
  • process.on('exit', cb):监听进程退出事件
  • process.on('uncaughtException', cb):监听未捕获异常

示例:

console.log('进程ID:', process.pid);
console.log('启动参数:', process.argv);
console.log('环境变量:', process.env.NODE_ENV);

2. child_process 模块

用于创建和管理子进程。常用方法:

  • exec:执行命令,适合简单命令行任务
  • spawn:启动新进程,流式处理标准输入/输出
  • fork:专门用于 Node.js 子模块,适合进程间通信

三、创建子进程的三种方式

1. child_process.exec

执行 shell 命令,返回完整结果。

const { exec } = require('child_process');

exec('ls -l', (err, stdout, stderr) => {
  if (err) {
    console.error('执行错误:', err);
    return;
  }
  console.log('输出:', stdout);
});

适合场景:执行简单命令,如文件操作、git 命令等。


2. child_process.spawn

启动新进程,适合处理大量数据流。

const { spawn } = require('child_process');

const child = spawn('node', ['-v']);

child.stdout.on('data', (data) => {
  console.log(`子进程输出: ${data}`);
});

child.stderr.on('data', (data) => {
  console.error(`错误输出: ${data}`);
});

child.on('close', (code) => {
  console.log(`子进程退出码: ${code}`);
});

适合场景:持续处理输入输出,如音视频流、文件处理等。


3. child_process.fork

专门用于 Node.js 子模块,支持进程间通信(IPC)。

主进程:

const { fork } = require('child_process');
const child = fork('child.js');

child.on('message', (msg) => {
  console.log('收到子进程消息:', msg);
});

child.send({ hello: 'world' });

子进程(child.js):

process.on('message', (msg) => {
  console.log('收到主进程消息:', msg);
  process.send({ received: true });
});

适合场景:多进程并发、任务分发、CPU 密集型计算。


四、进程间通信(IPC)

  • fork 创建的子进程可通过 send 方法进行消息通信。
  • 适合主进程分配任务,子进程处理后返回结果。

五、应用场景举例

  1. CPU 密集型任务:如加密、图片处理,将任务分发到多个子进程,避免主线程阻塞。
  2. 任务分片:如爬虫、批量处理,将任务拆分给不同子进程。
  3. 服务集群:利用 cluster 模块实现多进程负载均衡(每个进程监听同一个端口)。

六、cluster 模块(进阶)

cluster 用于创建多进程服务器,充分利用多核 CPU。

简单示例:

const cluster = require('cluster');
const http = require('http');
const os = require('os');

if (cluster.isMaster) {
  const cpuCount = os.cpus().length;
  for (let i = 0; i < cpuCount; i++) {
    cluster.fork();
  }
} else {
  http.createServer((req, res) => {
    res.end('Hello from worker ' + process.pid);
  }).listen(3000);
}

七、注意事项

  • 子进程是独立的内存空间,变量不会共享。
  • 进程间通信数据量大时建议采用流式处理。
  • 子进程过多可能导致系统资源耗尽,要合理管理。
  • 异常处理要完善,防止主进程或子进程崩溃。

八. 进程池的实现

问题: 如果每个任务都新建子进程,频繁创建/销毁会浪费资源。
解决方案: 建立“进程池”,维护一组固定数量的子进程,任务轮流分配。

简单进程池示例:

const { fork } = require('child_process');
const path = require('path');

class ProcessPool {
  constructor(size, workerPath) {
    this.pool = [];
    this.tasks = [];
    this.size = size;
    this.workerPath = workerPath;
    for (let i = 0; i < size; i++) {
      const worker = fork(workerPath);
      worker.busy = false;
      worker.on('message', (msg) => {
        worker.busy = false;
        if (this.tasks.length) {
          const task = this.tasks.shift();
          worker.busy = true;
          worker.send(task);
        }
      });
      this.pool.push(worker);
    }
  }

  runTask(data) {
    const idleWorker = this.pool.find(w => !w.busy);
    if (idleWorker) {
      idleWorker.busy = true;
      idleWorker.send(data);
    } else {
      this.tasks.push(data);
    }
  }
}

// worker.js
// process.on('message', (data) => {
//   // 处理任务
//   process.send({ done: true, data: data });
// });

// 用法
const pool = new ProcessPool(4, path.join(__dirname, 'worker.js'));
for (let i = 0; i < 10; i++) {
  pool.runTask({ num: i });
}

九. cluster 的高级应用

负载均衡与自动重启

cluster 可以自动分发请求到不同 worker。
如果 worker 崩溃,可以自动重启:

const cluster = require('cluster');
const os = require('os');

if (cluster.isMaster) {
  const cpuNum = os.cpus().length;
  for (let i = 0; i < cpuNum; i++) {
    cluster.fork();
  }
  cluster.on('exit', (worker, code, signal) => {
    console.log(`Worker ${worker.process.pid} died. Restarting...`);
    cluster.fork();
  });
} else {
  require('./app'); // 你的服务器代码
}

十. 子进程异常处理与自动重启

  • 监听 exiterror 事件,及时处理异常。
  • 可以设置最大重启次数,防止无限重启。

示例:

const { fork } = require('child_process');

function startWorker() {
  const worker = fork('./worker.js');
  worker.on('exit', (code) => {
    if (code !== 0) {
      console.log('Worker crashed, restarting...');
      startWorker();
    }
  });
}

startWorker();

十一. 实际案例:多进程爬虫

主进程分配任务,子进程爬取数据并返回结果。

master.js:

const { fork } = require('child_process');
const urls = [/* 一堆要爬的网址 */];
const workerNum = 4;
let finished = 0;

for (let i = 0; i < workerNum; i++) {
  const worker = fork('./crawler.js');
  worker.on('message', (msg) => {
    console.log('爬取结果:', msg);
    finished++;
    if (urls.length) {
      worker.send(urls.pop());
    } else if (finished >= workerNum) {
      process.exit();
    }
  });
  worker.send(urls.pop());
}

crawler.js:

const axios = require('axios');
process.on('message', async (url) => {
  try {
    const res = await axios.get(url);
    process.send({ url, status: res.status });
  } catch (e) {
    process.send({ url, error: e.message });
  }
});

十二. 性能与资源管理建议

  • 进程数量:一般建议不超过 CPU 核心数。
  • 内存管理:子进程内存独立,监控内存占用,及时回收。
  • 任务分配:主进程负责调度,合理分配任务,避免某个子进程过载。
  • 日志与监控:记录每个子进程的状态、异常、处理时间等,方便排查问题。
  • 优雅退出:进程收到 SIGINT/SIGTERM 时,优雅关闭,处理未完成任务。

 

十三. 进程间复杂通信

1.1 消息传递机制(IPC)

fork 创建的子进程和父进程可以通过 process.send() 和 process.on('message', ...) 进行消息通信。
消息是 JSON 序列化的对象,适合传递结构化数据。

示例:主进程收集子进程结果

// master.js
const { fork } = require('child_process');
const child = fork('./worker.js');

child.on('message', (msg) => {
  console.log('收到子进程消息:', msg);
});

child.send({ task: 'calculate', data: 123 });

// worker.js
process.on('message', (msg) => {
  // 处理任务
  let result = msg.data * 2;
  process.send({ result });
});

1.2 数据流通信

对于大量数据(如文件、视频流),推荐用 spawn,通过 stdin/stdout 流进行通信。

示例:父进程传文件内容给子进程处理

const { spawn } = require('child_process');
const fs = require('fs');

const child = spawn('node', ['worker.js']);
fs.createReadStream('bigfile.txt').pipe(child.stdin);
child.stdout.on('data', data => {
  console.log('处理结果:', data.toString());
});

1.3 共享内存(进阶)

Node.js 原生不支持进程间共享内存,但可以通过 worker_threads 使用 SharedArrayBuffer 实现线程级共享。
进程间如果需要共享状态,推荐用 Redis、数据库等外部存储。


十四. 子进程的资源限制与监控

2.1 设置资源限制

可通过启动参数控制子进程资源,如内存:

const child = fork('worker.js', [], { execArgv: ['--max-old-space-size=128'] }); // 限制最大内存128MB

2.2 监控进程健康

  • 定期检测子进程是否存活(process.kill(pid, 0) 检查是否存在)
  • 通过心跳消息机制,父进程定时要求子进程回复,超时则重启

心跳机制示例:

// master.js
setInterval(() => {
  child.send({ type: 'ping' });
  // 如果长时间未收到 'pong',则重启
}, 1000);

// worker.js
process.on('message', (msg) => {
  if (msg.type === 'ping') {
    process.send({ type: 'pong' });
  }
});

十五. 父子进程生命周期管理

  • 父进程应监听子进程的 exit 事件,及时重启或清理资源
  • 子进程可通过 process.exit() 主动退出
  • 父进程退出时,建议优雅关闭所有子进程(可用 SIGTERM 信号)

优雅退出示例:

process.on('SIGTERM', () => {
  // 清理资源
  process.exit(0);
});

十六. 多进程架构设计建议

  • 任务分配策略:轮询分配、按负载分配、优先空闲进程
  • 进程池管理:任务过多时排队,避免资源耗尽
  • 异常恢复机制:自动重启、报警、日志记录
  • 扩展性:进程池大小可动态调整,支持横向扩展
  • 与主线程通信协议:设计好消息格式,避免消息丢失或混乱

十七. 进程与线程的区别及混合使用

  • 进程:独立内存空间,适合分布式、并发任务、CPU密集型
  • 线程(worker_threads):共享内存,适合高频小任务、共享状态

混合使用场景:

  • 主进程调度任务,子进程负责大任务,子进程内部用 worker_threads 并行处理
  • 例如:视频转码、图片批量处理

worker_threads 简单示例:

const { Worker } = require('worker_threads');
const worker = new Worker('./thread.js', { workerData: { num: 123 } });
worker.on('message', msg => console.log('线程结果:', msg));

十八. 常见问题与解决办法

  • 子进程僵尸化:及时监听并回收
  • IPC消息丢失:设计重试机制
  • 内存泄漏:监控内存,定期重启进程
  • 进程间同步:用外部存储(Redis、数据库)做协调

十九. 推荐工具

  • PM2:自动进程管理、负载均衡、日志收集
  • node-cluster:多进程 HTTP 服务
  • Bull/Agenda:任务队列与分布式处理

创作不易,点赞关注,持续更新!!!

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猩火燎猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值