Node在选型时即基于V8构建,所以它与浏览器的模型类似,是运行在单个进程的单个线程上,这样的好处是程序状态比较单一,没有锁和线程同步的问题,但如今CPU基本都是多核的,真正的服务器上CPU往往还是多个。由于一个Node进程只能利用一个核,所以Node 单线程的架构没法充分利用当前服务器的多核 CPU 性能,因此 Node.js 提供了原生的cluster模块,可以充分利用多核CPU的性能,同时可以增加程序的稳定性。

1. child_process模块

但面对单线程无法充分利用多核优势的情况,前人的经验是启动多进程即可,理想情况下每个进程各自利用一个CPU,以此实现多核CPU的利用。Node提供了child_process模块,可以通过child_process.fork()实现进程的复制。

var fork = require('child_process').fork;
var cpus = reuqire('os').cpus();
 
for(var i = 0; i < cpus.length; i++) {
    fork('./worker.js');
}

这就是著名的Mater-Worker主从模式,其中主进程不负责具体的业务处理,而是负责调度或者管理工作进程,工作进程负责具体的业务处理。通过fork()复制的进程都是一个独立的进程,在这个进程中有着独立而全新的V8实例,它需要至少30ms的启动时间和至少10MB的内存,所以fork()进程是昂贵的。 在主从模式中,主进程为了实现对子进程的管理和调度,还需要在进程间进行通信,即Inter-Process Communication (IPC)。进程间通信的目的是为了让不同的进程能够互相访问资源并进行协调工作。实现进程间通信的技术有很多,如命名管道、匿名管道、Socket、信号量、共享内存、消息队列、Domain Socket等。

2. Cluster模块

Node在v0.8中新增了cluster模块,在v0.8版本之前实现多进程架构必须通过child_process来实现,而现在通过cluster模块提供的较完善的API就可以解决多核CPU的利用率问题。事实上cluster模块就是child_process和net模块的组合应用。

var cluster = require('cluster');
var http = require('http');
var numCPUs = require('os').cpus().length;
 
if (cluster.isMaster) {
  // Fork workers.
  for (var i = 0; i < numCPUs; i++) {
    cluster.fork();
  }
 
  cluster.on('exit', function(worker, code, signal) {
    console.log('worker ' + worker.process.pid + ' died');
  });
} else {
  // Workers can share any TCP connection
  // In this case its a HTTP server
  http.createServer(function(req, res) {
    res.writeHead(200);
    res.end("hello world\n");
  }).listen(8000);
}

3. Forever / PM2

  • Forever: A simple CLI tool for ensuring that a given node script runs continuously (i.e. forever)
  • PM2: Production process manager for Node.JS applications. Perfectly designed for microservice architecture.