node入门(二)核心API(八)
2015-11-24 16:32:02

131
概述
基本用法
Node.js默认单进程运行,对于32位系统最高可以使用512MB内存,对于64位最高可以使用1GB内存。对于多核CPU的计算机来说,这样做效率很低,因为只有一个核在运行,其他核都在闲置。cluster模块就是为了解决这个问题而提出的。
cluster模块允许设立一个主进程和若干个worker进程,由主进程监控和协调worker进程的运行。worker之间采用进程间通信交换消息,cluster模块内置一个负载均衡器,采用Round-robin算法协调各个worker进程之间的负载。运行时,所有新建立的链接都由主进程完成,然后主进程再把TCP连接分配给指定的worker进程。
javascript var cluster = require('cluster'); var os = require('os'); if (cluster.isMaster){ for (var i = 0, n = os.cpus().length; i < n; i += 1){ cluster.fork(); } } else { http.createServer(function(req, res) { res.writeHead(200); res.end("hello world\n"); }).listen(8000); }
上面代码先判断当前进程是否为主进程(cluster.isMaster),如果是的,就按照CPU的核数,新建若干个worker进程;如果不是,说明当前进程是worker进程,则在该进程启动一个服务器程序。
上面这段代码有一个缺点,就是一旦work进程挂了,主进程无法知道。为了解决这个问题,可以在主进程部署online事件和exit事件的监听函数。
javascript 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(); });
}
上面代码中,主进程一旦监听到worker进程的exit事件,就会重启一个worker进程。worker进程一旦启动成功,可以正常运行了,就会发出online事件。
worker对象
worker对象是cluster.fork()的返回值,代表一个worker进程。
它的属性和方法如下。
(1)worker.id
work.id返回当前worker的独一无二的进程编号。这个编号也是cluster.workers中指向当前进程的索引值。
(2)worker.process
所有的worker进程都是用child_process.fork()生成的。child_process.fork()返回的对象,就被保存在worker.process之中。通过这个属性,可以获取worker所在的进程对象。
(3)worker.send()
该方法用于在主进程中,向子进程发送信息。
javascript if (cluster.isMaster) { var worker = cluster.fork(); worker.send('hi there'); } else if (cluster.isWorker) { process.on('message', function(msg) { process.send(msg); }); }
上面代码的作用是,worker进程对主进程发出的每个消息,都做回声。
在worker进程中,要向主进程发送消息,使用process.send(message);要监听主进程发出的消息,使用下面的代码。
javascript process.on('message', function(message) { console.log(message); });
发出的消息可以字符串,也可以是JSON对象。下面是一个发送JSON对象的例子。
javascript worker.send({ type: 'task 1', from: 'master', data: { // the data that you want to transfer } });
cluster.workers对象
该对象只有主进程才有,包含了所有worker进程。每个成员的键值就是一个worker进程对象,键名就是该worker进程的worker.id属性。
javascript function eachWorker(callback) { for (var id in cluster.workers) { callback(cluster.workers[id]); } } eachWorker(function(worker) { worker.send('big announcement to all workers'); });
上面代码用来遍历所有worker进程。
当前socket的data事件,也可以用id属性识别worker进程。
javascript socket.on('data', function(id) { var worker = cluster.workers[id]; });
cluster模块的属性与方法
isMaster,isWorker
isMaster属性返回一个布尔值,表示当前进程是否为主进程。这个属性由process.env.NODE_UNIQUE_ID决定,如果process.env.NODE_UNIQUE_ID为未定义,就表示该进程是主进程。
isWorker属性返回一个布尔值,表示当前进程是否为work进程。它与isMaster属性的值正好相反。
fork()
fork方法用于新建一个worker进程,上下文都复制主进程。只有主进程才能调用这个方法。
该方法返回一个worker对象。
kill()
kill方法用于终止worker进程。它可以接受一个参数,表示系统信号。
如果当前是主进程,就会终止与worker.process的联络,然后将系统信号法发向worker进程。如果当前是worker进程,就会终止与主进程的通信,然后退出,返回0。
在以前的版本中,该方法也叫做 worker.destroy() 。
listening事件
worker进程调用listen方面以后,“listening”就传向该进程的服务器,然后传向主进程。
该事件的回调函数接受两个参数,一个是当前worker对象,另一个是地址对象,包含网址、端口、地址类型(IPv4、IPv6、Unix socket、UDP)等信息。这对于那些服务多个网址的Node应用程序非常有用。
javascript cluster.on('listening', function(worker, address) { console.log("A worker is now connected to " + address.address + ":" + address.port); });
不中断地重启Node服务
思路
重启服务需要关闭后再启动,利用cluster模块,可以做到先启动一个worker进程,再把原有的所有work进程关闭。这样就能实现不中断地重启Node服务。
首先,主进程向worker进程发出重启信号。
javascript workers[wid].send({type: 'shutdown', from: 'master'});
worker进程监听message事件,一旦发现内容是shutdown,就退出。
javascript process.on('message', function(message) { if(message.type === 'shutdown') { process.exit(0); } });
下面是一个关闭所有worker进程的函数。
javascript 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); }); };
实例
下面是一个完整的实例,先是主进程的代码master.js。
javascript var cluster = require('cluster'); console.log('started master with ' + process.pid); // 新建一个worker进程 cluster.fork(); process.on('SIGHUP', function () { console.log('Reloading...'); var new_worker = cluster.fork(); new_worker.once('listening', function () { // 关闭所有其他worker进程 for(var id in cluster.workers) { if (id === new_worker.id.toString()) continue; cluster.workers[id].kill('SIGTERM'); } }); });
上面代码中,主进程监听SIGHUP事件,如果发生该事件就关闭其他所有worker进程。之所以是SIGHUP事件,是因为nginx服务器监听到这个信号,会创造一个新的worker进程,重新加载配置文件。另外,关闭worker进程时,主进程发送SIGTERM信号,这是因为Node允许多个worker进程监听同一个端口。
下面是worker进程的代码server.js。
javascript var cluster = require('cluster'); if (cluster.isMaster) { require('./master'); return; } var express = require('express'); var http = require('http'); var app = express(); app.get('/', function (req, res) { res.send('ha fsdgfds gfds gfd!'); }); http.createServer(app).listen(8080, function () { console.log('http://localhost:8080'); });
使用时代码如下。
bash $ node server.js started master with 10538 http://localhost:8080
然后,向主进程连续发出两次SIGHUP信号。
bash $ kill -SIGHUP 10538 $ kill -SIGHUP 10538
主进程会连续两次新建一个worker进程,然后关闭所有其他worker进程,显示如下。
bash Reloading... http://localhost:8080 Reloading... http://localhost:8080
最后,向主进程发出SIGTERM信号,关闭主进程。
bash $ kill 10538
PM2模块
PM2模块是cluster模块的一个包装层。它的作用是尽量将cluster模块抽象掉,让用户像使用单进程一样,部署多进程Node应用。
javascript // app.js var http = require('http'); http.createServer(function(req, res) { res.writeHead(200); res.end("hello world"); }).listen(8080);
上面代码是标准的Node架设Web服务器的方式,然后用PM2从命令行启动这段代码。
javascript $ pm2 start app.js -i 4
上面代码的i参数告诉PM2,这段代码应该在cluster_mode启动,且新建worker进程的数量是4个。如果i参数的值是0,那么当前机器有几个CPU内核,PM2就会启动几个worker进程。
如果一个worker进程由于某种原因挂掉了,会立刻重启该worker进程。
bash # 重启所有worker进程 $ pm2 reload all
每个worker进程都有一个id,可以用下面的命令查看单个worker进程的详情。
bash $ pm2 show <worker id>
正确情况下,PM2采用fork模式新建worker进程,即主进程fork自身,产生一个worker进程。pm2 reload命令则会用spawn方式启动,即一个接一个启动worker进程,一个新的worker启动成功,再杀死一个旧的worker进程。采用这种方式,重新部署新版本时,服务器就不会中断服务。
bash $ pm2 reload <脚本文件名>
关闭worker进程的时候,可以部署下面的代码,让worker进程监听shutdown消息。一旦收到这个消息,进行完毕收尾清理工作再关闭。
javascript process.on('message', function(msg) { if (msg === 'shutdown') { close_all_connections(); delete_logs(); server.close(); process.exit(0); } });
Node.js 回调函数
Node.js 异步编程的直接体现就是回调。 异步编程依托于回调来实现,但不能说使用了回调后程序就异步化了。 回调函数在完成任务后就会被调用,Node 使用了大量的回调函数,Node 所有 API 都支持回调函数。 例如,我们可以一边读取文件,一边执行其他命令,在文件读取完成后,我们将文件内容作为回调函数的参数返回。这样在执行代码时就没有阻塞或等待文件 I/O 操作。这就大大提高了 Node.js 的性能,可以处理大量的并发请求。
阻塞代码实例
创建一个文件 input.txt ,内容如下:
珠峰培训 www.zhufengpeixun.cn
创建 main.js 文件, 代码如下:
var fs = require("fs"); var data = fs.readFileSync('input.txt'); console.log(data.toString()); console.log("程序执行结束!");
非阻塞代码实例
var fs = require("fs"); fs.readFile('input.txt', function (err, data) { if (err) return console.error(err); console.log(data.toString()); }); console.log("程序执行结束!");
以上两个实例我们了解了阻塞与非阻塞调用的不同。
第一个实例在文件读取完后才执行完程序。
第二个实例我们呢不需要等待文件读取完,这样就可以在读取文件时同时执行接下来的代码,大大提高了程序的性能。
因此,阻塞按是按顺序执行的,而非阻塞是不需要按顺序的,所以如果需要处理回调函数的参数,我们就需要写在回调函数内。
Node.js 事件循环
Node.js 是单进程单线程应用程序,但是通过事件和回调支持并发,所以性能非常高。 Node.js 的每一个 API 都是异步的,并作为一个独立线程运行,使用异步函数调用,并处理并发。 Node.js 基本上所有的事件机制都是用设计模式中观察者模式实现。 Node.js 单线程类似进入一个while(true)的事件循环,直到没有事件观察者退出,每个异步事件都生成一个事件观察者,如果有事件发生就调用该回调函数.
事件驱动程序
Node.js 使用事件驱动模型,当web server接收到请求,就把它关闭然后进行处理,然后去服务下一个web请求。 当这个请求完成,它被放回处理队列,当到达队列开头,这个结果被返回给用户。 这个模型非常高效可扩展性非常强,因为webserver一直接受请求而不等待任何读写操作。(这也被称之为非阻塞式IO或者事件驱动IO) 在事件驱动模型中,会生成一个主循环来监听事件,当检测到事件时触发回调函数。 整个事件驱动的流程就是这么实现的,非常简洁。有点类似于观察者模式,事件相当于一个主题(Subject),而所有注册到这个事件上的处理函数相当于观察者(Observer)。 Node.js 有多个内置的事件,我们可以通过引入 events 模块,并通过实例化 EventEmitter 类来绑定和监听事件,如下实例: 给一个按钮绑定一个点击事件,当点击之后回调回调函数。
// 引入events模块 var events = require('events'); // 创建按钮对象 var button = new events.EventEmitter(); // 绑定事件及事件的处理程序 button.on('click', function(){console.log('点击了按钮');}); // 触发事件 button.emit('click');