node实例是单线程作业的,在服务端编程中,通常会创建多个node实例来处理客户端的请求,以此来提升系统的吞吐率,对这样多个node 实例,称之为cludter
集群有以下两种常见的实现方案,但是node自带的cluster模块,采用了方案2
集群内的node实例,各自监听不同的端口,再由反向代理实现请求到多个端口的分发
集群内,创建一个主进程(master),以及若干个子进程(worker)。由master监听客户端连接请求,并根据特定的策略,转发给worker
举例:
创建与CPU数目相同的服务端实例,来处理客户端请求。他们监听的都是同样的端口
// server.js
var cluster = require("cluster");
var cpuNums = require("os").cpus().length;
var http = require("http");
if(cluster.isMaster){
for(var i = 0; i < cpuNums; i++){
cluster.fork();
}
}else{
http.createServe(function(req, res){
res.end(`response from worker ${process.pid}`);
}).listen(3000);
console.log(`worker ${process.pid} start`);
}
// 创建批处理脚本:./req.sh
#!/bin/bash
for((i=1;i<=4;i++)); do
curl http://127.0.0.1:3000 //发出GET请求
echo ""
done
//输出
response from worker 23735
response from worker 23731
response from worker 23729
response from worker 23730
master 进程通过 cluster.fork()来创建 worker 进程,cluster.fork()内部是通过 clild_process.fork()来创建子进程
const net = require("net");
const server = net.createServer();
net模块中,对listen()方法进行了特殊处理,根据当前进程是master进程还是worker进程:
注意:
主进程master并不会真正去监听端口,端口监听工作始终会交给主进程来完成,主进程在接到子进程worker发来的端口监听的时候,首先会判断是否有相同的服务器,如果有,就直接将子进程worker绑定到对应服务器上,这样就不会出现端口被占用的问题,如果没有对应的服务,就生成一个新的服务。主进程master接受请求时候,就会将请求任务分配给工作进程,如何分配,就需要看具体使用的哪种负载均衡了。
归纳:master进程监听特定端口,并将客户请求转发给worker进程
每当worker进程创建server实例来监听请求,都会通过IPC通道,在master上进行注册。当客户端请求到达,master会负责将请求转发给对应的worker
至于转发给那个worker,这个是由转发策略决定的,可以通过环境变量NODE_CLUSTER_SCHED_POLICY设置,也可以在 cluster.setupMaster(options)时传入,默认的转发策略是轮询(SCHED_RR)当有用户请求到达,master会轮询一遍worker列表,找到第一个空闲的worker,然后将请求转发给该worker