如何使用Cluster来提高Node.js的性能

573 阅读5分钟

介绍

Node.js是一个开源的、用于服务端的Javascript运行时框架,它能让你搭建一个易于扩展的开发环境。

Node.js提供的其中一个最佳解决方案就是将单个进程划分为几个子进程,称之为workers。通过cluster模块,一些复杂的进程可以被分割成更小、更简单的进程,从而显著提高Node应用的执行效率。

在本指南中,你将学到所有cluster的入门知识,并利用安装了Nodejs的多核操作系统来提高你的web服务器性能。

Cluster模块如何在Node.js上工作

集群(cluster)是Node中单个父进程的一些子进程(workers)的集合。

通过调用child_process模块的fork()方法,可以为父进程创建子进程,而父进程的任务就是控制它们。

要在你的应用中使用集群,你首先要在你的程序文件中导入这个模块,如下所示:

var cluster = require('cluster');

现在,需要在代码中指明哪一部分是父进程或主进程的,哪一部分是子进程的。

使用下面的语法来判断是否是主进程master

if(cluster.isMaster){...}

在花括号内,编写主进程要执行的指令,第一个指令就是使用fork()函数来初始化子进程:

cluster.fork();

在这个方法中,可以指定子进程相关的方法和属性。

cluster模块包含一些事件。最常见的是online,当一个子进程被激活时触发,而exit事件则在子进程被杀死时触发。

下面列举了一些集群相关使用示例。

示例

如何在Node.js应用中使用Cluster模块

在第一个示例中,搭建一个小型服务端应用,通过返回处理请求的worker进程ID来响应收到的请求,在本例中,父进程将由4个子进程组成。

var cluster = require('cluster');
var http = require('http');
var numCPUs = 4;

if (cluster.isMaster) {
 for (var i = 0; i < numCPUs; i++) {
  cluster.fork();
 }
} else {
 http.createServer(function(req, res) {
  res.writeHead(200);
  res.end('process ' + process.pid + ' says hello!');
 }).listen(8000);
}

将上述代码保存并执行。

$ node file_name.js

接着,通过IP地址和代码中指定的端口8000来访问服务器,控制这个程序的执行。

注意:要指定端口,只需输入“:port number”作为IP地址的后缀。

如何开发一个易于扩展的Express服务器

Express是最著名的Node.js web框架之一,它使用Javascript编写,并在Node.js中运行。

接下来的第二个示例将向你解释如何创建一个易于扩展的Express服务器,以及如何通过少量代码来让服务器使用集群。

var cluster = require('cluster');

if(cluster.isMaster) {
 var numWorkers = require('os').cpus().length;

 console.log('Master cluster setting up ' + numWorkers + ' workers...');

 for(var i = 0; i < numWorkers; i++) {
  cluster.fork();
 }

 cluster.on('online', function(worker) {
  console.log('Worker ' + worker.process.pid + ' is online');
 });

 cluster.on('exit', function(worker, code, signal) {
  console.log('Worker ' + worker.process.pid + ' died with code: ' + code + ', and signal: ' + signal);
  console.log('Starting a new worker');
  cluster.fork();
 });
} else {
 var app = require('express')();
 app.all('/*', function(req, res) {res.send('process ' + process.pid + ' says hello!').end();})

 var server = app.listen(8000, function() {
  console.log('Process ' + process.pid + ' is listening to all incoming requests');
 });
}

分析上述代码:

  • CPU的核数通过os模块来获得,该模块包含一个名为cpus()的函数,它返回一个包含CPU各个内核标识的数组,因此,CPU的核数就是这个数组的长度。知道了CPU核数我们就能知道该创建多少个worker,以此来优化系统性能。
  • 通过监听exit事件及给定一个回调函数,代码的另一部分描述了worker生命周期的结束时该干什么。在回调函数内,旧的worker被杀死,同时新的worker被创建,以此来保持worker的预期数量。
  • 最后,监听online事件,并设置worker被激活时的回调函数。

高级操作

主进程与子进程通信

为了分配任务及执行其他操作,管理主进程与子进之间的信息交换非常重要。

要查看这些消息,需要在主进程和子进程之间监听message事件:

worker.on('message', function(message){
 console.log(message);
});

上面的代码中,worker对象是fork()方法返回的一个引用。

现在,监听来自主进程的消息。

process.on('message', function(message) {
 console.log(message);
});

消息体可以是字符串或者JSON序列化对象,例如,主进程向子进程发送消息:

worker.send('hello from the master');

子进程向父进程发送消息:

process.send('hello from worker with id: ' + process.pid);

为了让消息体包含更多的信息,将它变成一个JSON对象,如下所示:

worker.send({
 type: 'task 1',
 from: 'master',
 data: {
  // the data that you want to transfer
 }
});

重置停机时间

使用clusters可以获得的最大好处就是零停机。

这可以通过worker进程自动重启来实现。这可以让你在加载应用的新更改前,让程序在前一个操作运行完。

为了实现这一功能,我们应该牢记以下准则:

  • master进程必须保持一直运行的状态,只有worker进程受到重启的影响。
  • master进程的功能必须单一,只负责管理它的子进程。
  • 需要一种机制来通知master进程重启worker进程。例如,您可以要求用户输入,或者监听文件的更改。

尽可能安全地重启一个worker进程,只有在其无响应时才杀死它。

要杀死worker进程,可以发送一条shutdown类型的消息:

workers[wid].send({type: 'shutdown', from: 'master'});

最后,指定接收到shutdown消息时该执行什么

process.on('message', function(message) {
 if(message.type === 'shutdown') {
  process.exit(0);
 }
});

上面的代码在收到shutdown指令后,立刻结束此子进程。管理子进程的逻辑必须由主进程控制。

function restartWorkers() {
 var wid, workerIds = [];

 for(wid in cluster.workers) {
  workerIds.push(wid);
 }

 workerIds.forEach(function(wid) {
  cluster.workers[wid].send({
 text: 'shutdown',
 from: 'master'
  });

  setTimeout(function() {
 if(cluster.workers[wid]) {
  cluster.workers[wid].kill('SIGKILL');
 }
  }, 5000);
 });
};

通过worker对象,你可以获得所有正在运行的线程id。

在上述代码中,定义了以下操作:

  • 获取当前运行的workersid,并将其保存到名为workerIds的数组中。
  • 安全关闭所有正在运行的worker
  • 如果5秒内无法安全worker进程,那么就强制关闭。

总结

总结

现在,你应该已经知道如何初步使用cluster来提高你的应用性能,不要忘记学习cluster的其他进阶特性,以此增强你的应用,避免问题和bugs