cluster原理
翻译为集群
上 https://www.cnblogs.com/dashnowords/p/10958457.html
下 https://www.cnblogs.com/dashnowords/p/11019089.html
https://yjhjstz.gitbooks.io/deep-into-node/content/chapter4/chapter4-1.html
https://zhuanlan.zhihu.com/p/157987519
背景: js是单线程, 无法充分利用服务器多核CPU的性能
- master, 主进程, 主管监听端口
- worker, 子进程, 由master进程fork出来的
- slave进程,(循环)分发任务给worker进程
集群实现方案 | 多个node实例+多个端口 | 主进程向子进程转发请求(node-cluster采用) |
---|---|---|
优点 | 实现简单,各实例相对独立,这对服务稳定性有好处 | 通常只占用一个端口,通信相对简单,转发策略更灵活 |
缺点 | 增加端口占用,进程之间通信比较麻烦 | 实现相对复杂,对主进程的稳定性要求较高 |
官网例子
// server.js
var cluster = require('cluster');
var cpuNums = require('os').cpus().length;
var http = require('http');
if(cluster.isMaster){
console.log(cpuNums); // 12
for(var i = 0; i < cpuNums; i++){
cluster.fork();
}
}else{
http.createServer(function(req, res){
res.end(`response from worker ${process.pid}`);
}).listen(3000);
console.log(`Worker ${process.pid} started`);
}
// 12
// Worker 68366 started
// Worker 68367 started
// Worker 68365 started
// 每访问 http://127.0.0.1:3000/ , 由不同的进程服务
master、worker如何通信?
master进程通过 cluster.fork() 来创建 worker进程。cluster.fork() 内部 是通过 child_process.fork() 来创建子进程。
- master进程、worker进程是父、子进程的关系。
- master进程、woker进程可以通过IPC通道进行通信。(重要)(而IPC通道内部是由libuv实现的)
- child.send方法不仅可以发送消息,还可以发送server handle(内部实际发送的是文件描述符)
// lib/internal/cluster/master.js
const { fork } = require('child_process');
function createWorkerProcess(id, env) {
return fork(cluster.settings.exec, cluster.settings.args, {
// ...
uid: cluster.settings.uid
});
}
多个server实例,如何实现端口共享?
master进程监听特定端口,并将客户请求转发给worker进程。 (统一由master进程监听并分发)
具体, ·net模块中,对 listen() 方法进行了特殊处理。根据当前进程是master进程,还是worker进程:
- master进程:在该端口上正常监听请求。(没做特殊处理)
- worker进程:创建server实例。然后通过IPC通道,向master进程发送消息,让master进程也创建 server 实例,并在该端口上监听请求。当请求进来时,master进程将请求转发给worker进程的server实例。
// lib/net.js
// ...
function listen(self, address, port, addressType, backlog, fd, exclusive) {
exclusive = !!exclusive;
if (!cluster) cluster = require('cluster');
if (cluster.isMaster || exclusive) {
self._listen2(address, port, addressType, backlog, fd);
return;
}
cluster._getServer(self, {
address: address,
port: port,
addressType: addressType,
fd: fd,
flags: 0
}, cb);
function cb(err, handle) {
// ...
self._handle = handle;
self._listen2(address, port, addressType, backlog, fd);
}
}
多个server实例,来自客户端的请求如何分发到多个worker?(转发策略)
Node.js 默认采用的策略是 round-robin 时间片轮转法
当有客户请求到达,master会轮询一遍worker列表,找到第一个空闲的worker,然后将该请求转发给该worker。
设置:
可以通过环境变量NODE_CLUSTER_SCHED_POLICY设置,也可以在cluster.setupMaster(options)时传入
// lib/cluster.js
RoundRobinHandle.prototype.distribute = function(err, handle) {
this.handles.push(handle);
var worker = this.free.shift();
if (worker) this.handoff(worker);
};
源码解析
https://github.com/nodejs/node/blob/v14.x/lib/cluster.js
https://github.com/nodejs/node/tree/v14.x/lib/internal/cluster