Node.JS

集群clusterfork运行Node.js

 使用Node.js最大的原因是因为简化,通过使用单线程范式,从而避免了多线程环境中复杂的线程编程,比如资源竞争和死锁。当然,以单线程方式运行一个Web服务器的缺点是只能利用单核CPU,而现在基本进入了多核时代,这就对资源造成了浪费,而通过Node.js的集群Cluster方式很容易更好地利用资源。

当你以集群方式运行Node.js时,将会跨多个处理器,这会更加稳定,因为在单流程情况下,当你的应用遭遇错误时,只能重新启动流程,在这段重启时间内,你的服务就中断了,集群方式可以保持一个主流程时刻了解它的子流程是否失败,会在其失败情况下自动将其请求转发到其他子流程,一直到失败的子流程重新启动。

加入集群非常容易,假设我们下面一段代码:

var express = require('express');
var app = express();
 
app.get('/', function(req, res) {
  res.send('Hello World!');
});
 
var server = app.listen(3000, function() {
  console.log('Server started on port 3000');
});

下面命令是是安装启动:

npm install express
node server

在浏览器打开http://localhost:3000会得到hello world。

下面是加入集群方式的代码:

var cluster = require('cluster');
 
if (cluster.isMaster) {
  var numCPUs = require('os').cpus().length;
 
  for (var i = 0; i < numCPUs; i++) {
    cluster.fork();
  }
} else {
  var express = require('express');
  var app = express();
 
  app.get('/', function(req, res) {
    res.send('Hello World!');
  });
 
  var server = app.listen(3000, function() {
    console.log('Server started on port 3000');
  });
}

这段代码主要增加了cluster,当应用启动后,将自己作为主流程,主流程根据CPU个数创建一个或多个子流程,一般情况下,子流程个数等于CPU的核数,主流程自己不是一个服务器,它只是负责创建和维护子流程,因为如果主流程也对外服务,一旦其失败,所有子流程都会失败。

重启

 如果一个子流程失败了,主流程能够重启启动它,代码如下:

var cluster = require('cluster');
 
if (cluster.isMaster) {
  var numCPUs = require('os').cpus().length;
 
  for (var i = 0; i < numCPUs; i++) {
    cluster.fork();
  }
 
  cluster.on('exit', function() {
    console.log('A worker process died, restarting...');
    cluster.fork();
  });
} else {
  var express = require('express');
  var app = express();
 
  app.get('/', function(req, res) {
    res.send('Hello World!');
  });
 
  app.get('/explode', function(req, res) {
    setTimeout(function() {
      res.send(this.wont.go.over.well);
    }, 1);
  });
 
  var server = app.listen(3000, function() {
    console.log('Server started on port 3000');
  });
}

当你访问http://localhost:3000/explode 时,你会引起一个子流程错误,会看到控制端的堆栈跟踪,但是你的应用还是在运行,但是如果你加载explode页面多次,你会杀死所有的子流程,应用就会退出。那么有灰色背景的一段代码就能够防止在子流程退出时重新启动。

性能测试

当我们使用8核集群方式进行性能测试,单流程结果如下:

Transactions:                  15932 hits
Availability:                 100.00 %
Elapsed time:                  19.41 secs
Data transferred:               0.18 MB
Response time:                  0.02 secs
Transaction rate:             820.64 trans/sec
Throughput:                     0.01 MB/sec
Concurrency:                   14.79
Successful transactions:       15932
Failed transactions:               0
Longest transaction:            0.03
Shortest transaction:           0.01

而启动了集群方式的8核性能如下:

Transactions:                  34479 hits
Availability:                 100.00 %
Elapsed time:                  19.38 secs
Data transferred:               0.39 MB
Response time:                  0.00 secs
Transaction rate:            1779.38 trans/sec
Throughput:                     0.02 MB/sec
Concurrency:                    7.93
Successful transactions:       34489
Failed transactions:               0
Longest transaction:            0.07
Shortest transaction:           0.00

注意到第一行数据对比,有116%的性能提高。当然Node.js不是CPU-bound重CPU计算的理想选择,真实世界的结果可能与这个测试有差距,如果你有依赖CPU计算的工作,可使用Node.js作为代理,委托给其他物理机器去执行。