cluster翻译可以为 集群。这也是node的翻译。
我们都知道js是单线程的语言,单个 Node.js 实例也运行在单个线程中。
为了充分利用多核系统,有时需要启用一组 Node.js 进程去处理负载任务(共享一个端口)。
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length; //获取cpu的数量,每颗核心开一个node进程,运行同一份代码。
if (cluster.isMaster) { // 一组进程往往需要一个master主进程引导
console.log(`主进程 ${process.pid} 正在运行`);
// 衍生工作进程。
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', (worker, code, signal) => { // 设置一个监听器,在node进程退出的时候触发
console.log(`工作进程 ${worker.process.pid} 已退出`);
});
} else {
// 工作进程可以共享任何 TCP 连接。
// 在本例子中,共享的是 HTTP 服务器。
http.createServer((req, res) => {
res.writeHead(200);
res.end('你好世界\n');
}).listen(8000);
console.log(`工作进程 ${process.pid} 已启动`);
}
以下是运行结果
$ node server.js
主进程 3596 正在运行
工作进程 4324 已启动
工作进程 4520 已启动
工作进程 6056 已启动
工作进程 5644 已启动
上面也看到了,其实他是fork出来的一个子进程,所以可以使用 IPC 和父进程通信,从而使各进程交替处理连接服务。
cluster 模块支持两种分发连接的方法。
-
第一种方法(也是除 Windows 外所有平台的默认方法)是循环法,由主进程负责监听端口,接收新连接后再将连接循环分发给工作进程,在分发中使用了一些内置技巧防止工作进程任务过载。(以上的例子便是)
-
第二种方法是,主进程创建监听 socket 后发送给感兴趣的工作进程,由工作进程负责直接接收连接。
理论上第二种方法应该是效率最佳的。 但在实际情况下,由于操作系统调度机制的随机,会使分发变得不稳定。 可能会出现八个进程中有两个分担了 70% 的负载。其他的空闲出来。
因为 server.listen() 将大部分工作交给主进程完成,因此导致普通 Node.js 进程与 cluster 工作进程差异的情况有三种:
- server.listen({fd: 7}) 因为消息会被传给主进程,所以父进程中的文件描述符 7 将会被监听并将句柄传给工作进程,而不是监听文件描述符 7 指向的工作进程。
- server.listen(handle) 显式地监听句柄,会导致工作进程直接使用该句柄,而不是和主进程通信(如例子)。
- server.listen(0) 正常情况下,这种调用会导致 server 在随机端口上监听。 但在 cluster 模式中,所有工作进程每次调用 listen(0) 时会收到相同的“随机”端口。 实质上,这种端口只在第一次分配时随机,之后就变得可预料。 如果要使用独立端口的话,应该根据工作进程的 ID 来生成端口号。
-
Worker 类
对象包含了关于工作进程的所有的公共的信息和方法。
在主进程中,可以使用 cluster.workers 来获取它。
在工作进程中,可以使用 cluster.worker 来获取它。
-
'disconnect' 事件
类似于 cluster.on('disconnect'),但他不是全局的,而是特定于单独某个工作进程。
cluster.fork().on('disconnect', () => {
// 工作进程已断开连接。
});
-
'exit' 事件
类似于 cluster.on('exit') 事件,但他不是全局的,而是特定于单独某个工作进程。
// code是正常退出的代码,signal是不正常退出的信号
cluster.fork().on('exit', (code, signal) => {
if (signal) {
console.log(`工作进程已被信号 ${signal} 杀死`);
} else if (code !== 0) {
console.log(`工作进程退出,退出码: ${code}`);
} else {
console.log('工作进程成功退出');
}
});
-
'listening' 事件
类似于 cluster.on('listening') 事件,但他不是全局的,而是特定于单独某个工作进程。
cluster.fork().on('listening', (address) => {
// 工作进程正在监听。
});
-
'message' 事件
类似于 cluster.on('message') 事件,但特定于此工作进程。
在工作进程内,也可以使用 process.on('message')。
const cluster = require('cluster');
const http = require('http');
if (cluster.isMaster) { // 主线程计数
let numReqs = 0; // 计数
setInterval(() => { // 第二个循环再打印,给子进程执行时间
console.log(`请求的数量 = ${numReqs}`);
}, 1000);
// 对请求计数的函数。
function messageHandler(msg) {
if (msg.cmd && msg.cmd === 'notifyRequest') {
numReqs += 1;
}
}
// 启动 worker 并监听包含 notifyRequest 的消息。
const numCPUs = require('os').cpus().length;
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
for (const id in cluster.workers) {
cluster.workers[id].on('message', messageHandler); // 监听子进程的消息
}
} else {
// 工作进程有一个 http 服务器。
http.Server((req, res) => {
res.writeHead(200);
res.end('你好世界\n');
// 通知主进程接收到了请求。
process.send({ cmd: 'notifyRequest' }); // 子进程会走到这个逻辑发送消息。
}).listen(8000);
}
未完待续。。。