写点什么

在 nodejs 中创建 cluster

发布于: 2021 年 01 月 31 日

简介

在前面的文章中,我们讲到了可以通过 worker_threads 来创建新的线程,可以使用 child_process 来创建新的子进程。本文将会介绍如何创建 nodejs 的集群 cluster。


cluster 集群

我们知道,nodejs 的 event loop 或者说事件响应处理器是单线程的,但是现在的 CPU 基本上都是多核的,为了充分利用现代 CPU 多核的特性,我们可以创建 cluster,从而使多个子进程来共享同一个服务器端口。


也就是说,通过 cluster,我们可以使用多个子进程来服务处理同一个端口的请求。


先看一个简单的 http server 中使用 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{worker.process.pid} 已退出`); });} else { // 工作进程可以共享任何 TCP 连接。 // 在本例子中,共享的是 HTTP 服务器。 http.createServer((req, res) => { res.writeHead(200); res.end('你好世界\n'); }).listen(8000);
console.log(`工作进程 ${process.pid} 已启动`);}
复制代码

cluster 详解

cluster 模块源自于 lib/cluster.js,我们可以通过 cluster.fork()来创建子工作进程,用来处理主进程的请求。


cluster 中的 event

cluster 继承自 events.EventEmitter,所以 cluster 可以发送和接收 event。


cluster 支持 7 中 event,分别是 disconnect,exit,fork,listening,message,online 和 setup。


在讲解 disconnect 之前,我们先介绍一个概念叫做 IPC,IPC 的全称是 Inter-Process Communication,也就是进程间通信。


IPC 主要用来进行主进程和子进程之间的通信。一个工作进程在创建后会自动连接到它的主进程。 当 ‘disconnect’ 事件被触发时才会断开连接。


触发 disconnect 事情的原因有很多,可以是主动调用 worker.disconnect(),也可以是工作进程退出或者被 kill 掉。


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

exit 事件会在任何一个工作进程关闭的时候触发。一般用来监测 cluster 中某一个进程是否异常退出,如果退出的话使用 cluster.fork 创建新的进程,以保证有足够多的进程来处理请求。


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);});
复制代码

主进程和工作进程的 listening 事件都会在工作进程调用 listen 方法的时候触发。


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

其中 worker 代表的是工作线程,而 address 中包含三个属性:address、 port 和 addressType。 其中 addressType 有四个可选值:


  • 4 (TCPv4)

  • 6 (TCPv6)

  • -1 (Unix 域 socket)

  • ‘udp4’ or ‘udp6’ (UDP v4 或 v6)

message 事件会在主进程收到子进程发送的消息时候触发。


当主进程生成工作进程时会触发 fork,当工作进程运行时会触发 online。


setupMaster 方法被调用的时候,会触发 setup 事件。


cluster 中的方法

cluster 中三个方法,分别是 disconnect,fork 和 setupMaster。


cluster.disconnect([callback])
复制代码

调用 cluster 的 disconnect 方法,实际上会在 cluster 中的每个 worker 中调用 disconnect 方法。从而断开 worker 和主进程的连接。


当所有的 worker 都断开连接之后,会执行 callback。


cluster.fork([env])
复制代码

fork 方法,会从主进程中创建新的子进程。其中 env 是要添加到进程环境变量的键值对。


fork 将会返回一个 cluster.Worker 对象,代表工作进程。


最后一个方法是 setupMaster:


cluster.setupMaster([settings])
复制代码

默认情况下,cluster 通过 fork 方法来创建子进程,但是我们可以通过 setupMaster 来改变这个行为。通过设置 settings 变量,我们可以改变后面 fork 子进程的行为。


我们看一个 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 中的属性

通过 cluster 对象,我们可以通过 isMaster 和 isWorker 来判断进程是否主进程。


可以通过 worker 来获取当前工作进程对象的引用:


const cluster = require('cluster');
if (cluster.isMaster) { console.log('这是主进程'); cluster.fork(); cluster.fork();} else if (cluster.isWorker) { console.log(`这是工作进程 #${cluster.worker.id}`);}
复制代码

可以通过 workers 来遍历活跃的工作进程对象:


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

每个 worker 都有一个 id 编号,用来定位该 worker。


cluster 中的 worker

worker 类中包含了关于工作进程的所有的公共的信息和方法。cluster.fork 出来的就是 worker 对象。


worker 的事件和 cluster 的很类似,支持 6 个事件:disconnect,error,exit,listening,message 和 online。


worker 中包含 3 个属性,分别是:id,process 和 exitedAfterDisconnect。


其中 id 是 worker 的唯一标记。


worker 中的 process,实际上是 ChildProcess 对象,是通过 child_process.fork()来创建出来的。


因为在 worker 中,process 属于全局变量,所以我们可以直接在 worker 中使用 process 来进行发送消息。


exitedAfterDisconnect 表示如果工作进程由于 .kill() 或 .disconnect() 而退出的话,值就是 true。如果是以其他方式退出的话,返回值就是 false。如果工作进程尚未退出,则为 undefined。


我们可以通过 worker.exitedAfterDisconnect 来区分是主动退出还是被动退出,主进程可以根据这个值决定是否重新生成工作进程。


cluster.on('exit', (worker, code, signal) => {  if (worker.exitedAfterDisconnect === true) {    console.log('这是自发退出,无需担心');  }});
// 杀死工作进程。worker.kill();
复制代码

worker 还支持 6 个方法,分别是:send,kill,destroy,disconnect,isConnected,isDead。


这里我们主要讲解一下 send 方法来发送消息:


worker.send(message[, sendHandle[, options]][, callback])
复制代码

可以看到 send 方法和 child_process 中的 send 方法参数其实是很类似的。而本质上,worker.send 在主进程中,这会发送消息给特定的工作进程。 相当于 ChildProcess.send()。在工作进程中,这会发送消息给主进程。 相当于 process.send()。


if (cluster.isMaster) {  const worker = cluster.fork();  worker.send('你好');
} else if (cluster.isWorker) { process.on('message', (msg) => { process.send(msg); });}
复制代码

在上面的例子中,如果是在主进程中,那么可以使用 worker.send 来发送消息。而在子进程中,则可以使用 worker 中的全局变量 process 来发送消息。


总结

使用 cluster 可以充分使用多核 CPU 的优势,希望大家在实际的项目中应用起来。


本文作者:flydean 程序那些事

本文链接:http://www.flydean.com/nodejs-cluster/

本文来源:flydean 的博客

欢迎关注我的公众号:「程序那些事」最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!


发布于: 2021 年 01 月 31 日阅读数: 15
用户头像

关注公众号:程序那些事,更多精彩等着你! 2020.06.07 加入

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧,尽在公众号:程序那些事!

评论

发布
暂无评论
在nodejs中创建cluster