Nodejs之cluster

只谈情不闲聊 提交于 2020-01-23 05:20:59

cluster

集群
单个Nodejs实例运行在单个线程中,为充分利用多核系统,需要启用一组Node进程处理负载任务。

cluster允许建立一个主进程和若干个worker进程,由主进程监控和协调worker进程的运行。
worker之间采用进程通信交换消息,cluster模块内置一个负载均衡。

cluster集成两个方面:

  • 集成了child_process.fork方法创建node子进程的方式;
  • 继承了很多多核cpu创建子进程后,自动控制负载均衡的方式;

cluster模块可以创建共享服务器端口的子进程。

const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;

if (cluster.isMaster) {
  console.log(`主进程 ${process.pid} 正在运行`);

  // 衍生工作进程。
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }

  cluster.on('exit', (worker, code, signal) => {
    console.log(`工作进程 ${worker.process.pid} 已退出`);
  });
} else {
  // 工作进程可以共享任何 TCP 连接。
  // 在本例子中,共享的是 HTTP 服务器。
  http.createServer((req, res) => {
    res.writeHead(200);
    res.end('你好世界\n');
  }).listen(8000);

  console.log(`工作进程 ${process.pid} 已启动`);
}

主进程 3596 正在运行
工作进程 4324 已启动
工作进程 4520 已启动
工作进程 6056 已启动
工作进程 5644 已启动

工作原理

cluster支持两种分发连接方法:

  1. 循环法,由主进程负责监听端口,接收新连接后再将连接循环发给工作进程,在分发中使用了一些内置技巧防止工作进程中任务过载;
  2. 主进程创建监听socket后发送给感兴趣的工作进程,由工作进程负责直接接收连接;

Worker类

Worker对象包含了关于工作进程的所有的公共的信息和方法。
主进程中,使用cluster.workers获取
工作进程中,使用cluster.worker获取

disconnect事件

断开连接

error事件

工作进程中,可以使用process.on(‘error’)

exit事件

const worker = cluster.fork();
worker.on('exit', (code, signal) => {
  if (signal) {
    console.log(`工作进程已被信号 ${signal} 杀死`);
  } else if (code !== 0) {
    console.log(`工作进程退出,退出码: ${code}`);
  } else {
    console.log('工作进程成功退出');
  }
});

listening事件

cluster.fork().on('listening', (address) => {
  // 工作进程正在监听。
});

message事件

在工作进程内,也可以使用 process.on(‘message’)

online事件

cluster.fork().on('online', () => {
  // 工作进程已上线。
});

worker.disconnect()

在一个工作进程内,调用此方法会关闭所有的 server,并等待这些 server 的 ‘close’ 事件执行,然后关闭 IPC 管道。

在主进程内,会给工作进程发送一个内部消息,导致工作进程自身调用 .disconnect()

worker.exitedAfterDisconnect

如果工作进程由于 .kill() 或 .disconnect() 而退出,则此属性为 true。
如果工作进程以任何其他方式退出,则为 false。
如果工作进程尚未退出,则为 undefined。

cluster.on('exit', (worker, code, signal) => {
  if (worker.exitedAfterDisconnect === true) {
    console.log('这是自发退出,无需担心');
  }
});

// 杀死工作进程。
worker.kill();

worker.id

每一个新衍生的工作进程都会被赋予自己独一无二的编号,这个编号就是储存在 id 里面。

worker.isConnected()

当工作进程通过 IPC 管道连接至主进程时,这个方法返回 true,否则返回 false。
一个工作进程在创建后会自动连接到它的主进程。
当 ‘disconnect’ 事件被触发时才会断开连接。

worker.isDead()

当工作进程被终止时(包括自动退出或被发送信号),这个方法返回 true。 否则,返回 false。

worker.kill([signal=‘SIGTERM’])

杀死工作进程。
主进程,通过断开与worker.process的连接来实现,一旦断开连接,通过signal杀死工作进程;
工作进程,通过断开IPC管道来实现,然后以代码0退出进程;

worker.process

所有的工作都通过child_process.fork()创建,返回的对象被存储为.process。工作进程中,process属于全局对象。

当process发生disconnect事件,且.exitedAfterDisconnect的值不是true时,工作进程会调用process.exit(0)来防止连接意外断开。

worker.send()

发送消息给主进程或工作进程。

if (cluster.isMaster) {
  const worker = cluster.fork();
  worker.send('你好');

} else if (cluster.isWorker) {
  process.on('message', (msg) => {
    process.send(msg);
  });
}

disconnect事件

工作进程的IPC管道被断开后触发。
可能导致触发的原因:

  • 工作进程优雅的退出;
  • 被杀死;
  • 手动断开连接;

‘disconnect’ 和 ‘exit’ 事件之间可能存在延迟。 这些事件可以用来检测进程是否在清理过程中被卡住,或是否存在长时间运行的连接。

cluster.on('disconnect', (worker) => {
  console.log(`工作进程 #${worker.id} 已断开连接`);
});

exit事件

当任何一个工作进程关闭的时候,cluster模块都会触发exit事件。

cluster.on('exit', (worker, code, signal) => {
  console.log('工作进程 %d 关闭 (%s). 重启中...',
              worker.process.pid, signal || code);
  cluster.fork();
});

fork事件

当新的工作进程被衍生时,cluster触发fork事件。
可以被用来记录工作进程活动,并产生一个自定义的超时。

const timeouts = [];
function errorMsg() {
  console.error('连接出错');
}

cluster.on('fork', (worker) => {
  timeouts[worker.id] = setTimeout(errorMsg, 2000);
});
cluster.on('listening', (worker, address) => {
  clearTimeout(timeouts[worker.id]);
});
cluster.on('exit', (worker, code, signal) => {
  clearTimeout(timeouts[worker.id]);
  errorMsg();
});

listening事件

当工作进程调用listen()后,工作进程上的server会触发listening事件,
同时主进程上的cluster会触发listening事件。

worker 包含了工作进程对象
address 包含了以下的连接属性:address、 port 和 addressType。 

cluster.on('listening', (worker, address) => {
  console.log(
    `工作进程已连接到 ${address.address}:${address.port}`);
});

addressType 可选值包括:

  • 4 (TCPv4)
  • 6 (TCPv6)
  • -1 (Unix 域 socket)
  • ‘udp4’ or ‘udp6’ (UDP v4 或 v6)

message事件

当集群主进程从任何工作进程接收到消息时触发。

online事件

当衍生一个新的工作进程后,工作进程会响应一个上线消息。
当主进程收到上线消息后悔触发此事件。

当主进程衍生工作进程时触发fork;
当工作进程运行时触发online;

cluster.on('online', (worker) => {
  console.log('工作进程被衍生后响应');
});

setup事件

每当.setupMaster()被调用时触发。

cluster.disconnect()

cluster.workers的每个工作进程中调用.disconnect()

cluster.fork()

衍生出一个新的工作进程,只能通过主进程调用。

cluster.isMaster

如果进程是主进程,则为true。

cluster.isWorker

如果进程不是主进程,则为true。

cluster.schdulingPolicy

调度策略。

cluster.settings

调用.setupMaster()或.fork()之后,这个配置对象将会包含这些配置项,包括默认值。

cluster.setupMaster()

setupMaster用于修改默认的fork行为,一旦调用,会按照cluster.settings进行设置。
所有的设置只对后来的.fork()调用有效,对之前的工作进程无影响。
唯一无法通过.setupMaster()设置的属性是传给.fork()的env属性。
只能由主进程调用。

上述的默认值只在第一次调用时有效,当后续调用时,将采用 cluster.setupMaster() 调用时的当前值。
const cluster = require('cluster');
cluster.setupMaster({
  exec: 'worker.js',
  args: ['--use', 'https'],
  silent: true
});
cluster.fork(); // https 工作进程
cluster.setupMaster({
  exec: 'worker.js',
  args: ['--use', 'http']
});
cluster.fork(); // http 工作进程

cluster.worker

当前工作进程对象的引用,对于主进程是无用的。

const cluster = require('cluster');

if (cluster.isMaster) {
  console.log('这是主进程');
  cluster.fork();
  cluster.fork();
} else if (cluster.isWorker) {
  console.log(`这是工作进程 #${cluster.worker.id}`);
}

cluster.workers

一个哈希表,储存了活跃的工作进程对象,id作为键名。
只能在主进程中调用。
工作进程断开连接及退出后,将会从cluster.workers里面移除。
移除工作在disconnect和exit两个事件中的最后一个触发之前完成。

// 遍历所有工作进程。
function eachWorker(callback) {
  for (const id in cluster.workers) {
    callback(cluster.workers[id]);
  }
}
eachWorker((worker) => {
  worker.send('通知所有工作进程');
});

使用工作进程的唯一 id 是定位工作进程最简单的方式。
socket.on('data', (id) => {
  const worker = cluster.workers[id];
});

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!