概述

Node.js默认单进程运行,对于32位系统最高可以使用512MB内存,对于64位最高可以使用1GB内存。对于多核CPU的计算机来说,这样做效率很低,因为只有一个核在运行,其他核都在闲置。cluster模块就是为了解决这个问题而提出的。

cluster模块允许设立一个主进程和若干个worker进程,由主进程监控和协调worker进程的运行。worker之间采用进程间通信交换消息,cluster模块内置一个负载均衡器,采用Round-robin算法协调各个worker进程之间的负载。运行时,所有新建立的链接都由主进程完成,然后主进程再把TCP连接分配给指定的worker进程。

基本示例

  1. const cluster = require('cluster');
  2. const http = require('http');
  3. const numCPUs = require('os').cpus().length;
  4. if (cluster.isMaster) {
  5. console.log(`Master ${process.pid} is running`);
  6. // Fork workers.
  7. for (let i = 0; i < numCPUs; i++) {
  8. cluster.fork();
  9. }
  10. cluster.on('exit', (worker, code, signal) => {
  11. console.log(`worker ${worker.process.pid} died`);
  12. });
  13. } else {
  14. // Workers can share any TCP connection
  15. // In this case it is an HTTP server
  16. http.createServer((req, res) => {
  17. res.writeHead(200);
  18. res.end('hello world\n');
  19. }).listen(8000);
  20. console.log(`Worker ${process.pid} started`);
  21. }

上面代码先判断当前进程是否为主进程(cluster.isMaster),如果是的,就按照CPU的核数,新建若干个worker进程;如果不是,说明当前进程是worker进程,则在该进程启动一个服务器程序。

现在,运行Node.js将在工作进程之间共享端口8000:

  1. $ node server.js
  2. Master 3596 is running
  3. Worker 4324 started
  4. Worker 4520 started
  5. Worker 6056 started
  6. Worker 5644 started

cluster模块的属性与方法

isMaster,isWorker

sMaster属性返回一个布尔值,表示当前进程是否为主进程。这个属性由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进程调用listening方法以后,“listening”事件就传向该进程的服务器,然后传向主进程。

该事件的回调函数接受两个参数,一个是当前worker对象,另一个是地址对象,包含网址、端口、地址类型(IPv4、IPv6、Unix socket、UDP)等信息。这对于那些服务多个网址的Node应用程序非常有用。

  1. cluster.on('listening', function (worker, address) {
  2. console.log("A worker is now connected to " + address.address + ":" + address.port);
  3. });

主进程cluster.workers对象

该对象只有主进程才有,包含了所有worker进程。每个成员的键值就是一个worker进程对象,键名就是该worker进程的worker.id属性。

  1. function eachWorker(callback) {
  2. for (var id in cluster.workers) {
  3. callback(cluster.workers[id]);
  4. }
  5. }
  6. eachWorker(function(worker) {
  7. worker.send('big announcement to all workers');
  8. });

上面代码用来遍历所有worker进程。

worker对象的属性与方法

worker对象是cluster.fork() 的返回值,代表一个worker进程。

worker.id

worker.id返回当前worker的独一无二的进程编号。这个编号也是cluster.workers中指向当前进程的索引值。

worker.process

所有的worker进程都是用child_process.fork()生成的。child_process.fork()返回的对象,就被保存在worker.process之中。通过这个属性,可以获取worker所在的进程对象。

worker.send()

该方法用于在主进程中,向子进程发送信息。

  1. if (cluster.isMaster) {
  2. var worker = cluster.fork();
  3. worker.send('hi there');
  4. } else if (cluster.isWorker) {
  5. process.on('message', function(msg) {
  6. process.send(msg);
  7. });
  8. }

上面代码的作用是,worker进程对主进程发出的每个消息,都做回声。
在worker进程中,要向主进程发送消息,使用process.send(message);要监听主进程发出的消息,使用下面的代码。

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

发出的消息可以字符串,也可以是JSON对象。下面是一个发送JSON对象的例子。

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

参考资料

  1. 《Node.js v12.16.1 Documentation》
  2. 阮一峰的Node.js文档《Cluster模块》
  3. 《Node.js进阶:cluster模块深入剖析》