- 原文地址:How to use Cluster to increase Node.js performance
- 原文作者:arubacloud
- 译文出自:掘金翻译计划
介绍
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。
在上述代码中,定义了以下操作:
- 获取当前运行的
workers的id,并将其保存到名为workerIds的数组中。 - 安全关闭所有正在运行的
worker。 - 如果5秒内无法安全
worker进程,那么就强制关闭。
总结
总结
现在,你应该已经知道如何初步使用cluster来提高你的应用性能,不要忘记学习cluster的其他进阶特性,以此增强你的应用,避免问题和bugs。