cluster模块概览
node实例是单线程作业的。在服务端编程中,通常会创建多个node实例来处理客户端的请求,以此提升系统的吞吐率。对这样多个node实例,我们称之为cluster(集群)。
借助node的cluster模块,开发者可以在几乎不修改原有项目代码的前提下,获得集群服务带来的好处。
集群有以下两种常见的实现方案,而node自带的cluster模块,采用了方案二。
方案一:多个node实例+多个端口
集群内的node实例,各自监听不同的端口,再由反向代理实现请求到多个端口的分发。
@H_301_16@方案二:主进程向子进程转发请求
集群内,创建一个主进程(master),以及若干个子进程(worker)。由master监听客户端连接请求,并根据特定的策略,转发给worker。
@H_301_16@入门实例
在cluster模块中,主进程称为master,子进程称为worker。
例子如下,创建与cpu数目相同的服务端实例,来处理客户端请求。注意,它们监听的都是同样的端口。
if(cluster.isMaster){
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
);
}
创建批处理脚本:./req.sh。
for((i=1;i<=4;i++)); do
curl http://127.0.0.1:3000
echo ""
done
输出如下。可以看到,响应来自不同的进程。
response from worker 23735 response from worker 23731 response from worker 23729 response from worker 23730
cluster模块实现原理
了解cluster模块,主要搞清楚3个问题:
@H_301_16@下面会结合示意图进行介绍,源码级别的介绍,可以参考 笔者的github。
问题1:master、worker如何通信
这个问题比较简单。master进程通过 cluster.fork() 来创建 worker进程。cluster.fork() 内部 是通过 child_process.fork() 来创建子进程。
也就是说:
@H_301_16@问题2:如何实现端口共享
在前面的例子中,多个woker中创建的server监听了同个端口3000。通常来说,多个进程监听同个端口,系统会报错。
为什么我们的例子没问题呢?
秘密在于,net模块中,对 listen() 方法进行了特殊处理。根据当前进程是master进程,还是worker进程:
@H_301_16@归纳起来,就是:master进程监听特定端口,并将客户请求转发给worker进程。
如下图所示:
问题3:如何将请求分发到多个worker
每当worker进程创建server实例来监听请求,都会通过IPC通道,在master上进行注册。当客户端请求到达,master会负责将请求转发给对应的worker。
具体转发给哪个worker?这是由转发策略决定的。可以通过环境变量NODE_CLUSTER_SCHED_POLICY设置,也可以在cluster.setupMaster(options)时传入。
默认的转发策略是轮询(SCHED_RR)。
当有客户请求到达,master会轮询一遍worker列表,找到第一个空闲的worker,然后将该请求转发给该worker。
master、worker内部通信小技巧
在开发过程中,我们会通过 process.on('message',fn) 来实现进程间通信。
前面提到,master进程、worker进程在server实例的创建过程中,也是通过IPC通道进行通信的。那会不会对我们的开发造成干扰呢?比如,收到一堆其实并不需要关心的消息?
答案肯定是不会?那么是怎么做到的呢?
当发送的消息包含cmd字段,且改字段以NODE_作为前缀,则该消息会被视为内部保留的消息,不会通过message事件抛出,但可以通过监听'internalMessage'捕获。
以worker进程通知master进程创建server实例为例子。worker伪代码如下:
master伪代码如下:
相关链接
官方文档:https://nodejs.org/api/cluster.html
原文链接:https://www.f2er.com/nodejs/32195.html