微信公众号:【前端一锅煮】一点技术,一点思考。有问题或建议,欢迎留言公众号。Node.js的主线程是单线程的。如果我们使用nodeapp.js运行,会启动一个进程,只能在一个CPU中计算,不能应用服务器的多核CPU。为了解决这个问题,我们可以采用多进程分发策略,即主进程接收所有的请求,然后通过一定的负载均衡策略分发到不同的Node.js子进程中。这种实现有两种不同的解决方案:主进程监听一个端口,子进程不监听该端口。主进程通过负载均衡技术将请求分发给子进程;主进程和子进程分别监听不同的端口,通过主进程向子进程分发请求。过程。第一种解决方案使用多个Node进程来监视同一个端口。优点是进程间通信比较简单,减少了端口资源的浪费。但是这个时候需要保证服务进程的稳定,尤其是Master进程的稳定。越高,编码会越复杂。第二种方案的问题是占用多个端口,造成资源浪费。由于多个实例独立运行,进程间通信不好做。优点是稳定性高,实例之间没有影响。Node.js自带的Cluster模块采用第一种方案。集群模式集群模式其实就是一个主进程和多个子进程,这样就形成了集群的概念。我们先来看一个集群模式的应用实例。先实现一个简单的app.js,代码如下:consthttp=require('http');constcluster=require('集群');常量实例=2;//启动进程数if(cluster.isMaster){for(leti=0;i{res.write(`helloworld,startwithcluster${process.pid}`);res.end();});//启动服务server.listen(8000,()=>{console.log('serverstarthttp://127.0.0.1:8000');});console.log(`Worker${process.pid}started`);}首先判断是否为主进程:如果是,则使用cluster.fork创建子进程;如果不需要子进程的特定app.js。然后运行以下命令启动服务。nodecluster.js启动成功后,再打开一个命令行窗口,多次运行如下命令:curl"http://127.0.0.1:8000/"可以看到如下输出:helloworld,startwithcluster8553helloworld,以8552helloworld集群开始,以8553helloworld集群开始,以8552集群开始后的进程ID是一个比较规则的随机数,有时输出8553,有时输出8552,8553和8552是上面fork出来的两个子进程,让我们看看为什么会这样。原理首先,我们需要明确两个问题:Node.js集群是如何实现多个进程监听一个端口的;Node.js是如何进行负载均衡请求分发的。主进程判断集群模式下有master和worker概念,master为主进程,worker为子进程。源码中判断是主进程还是子进程的逻辑如下:'usestrict';process.env中的constchildOrPrimary='NODE_UNIQUE_ID'?“孩子”:“主要”;module.exports=require(`internal/cluster/${childOrPrimary}`);从进程环境变量设置来看:如果不设置,则为master进程;如果已设置,则为子进程。因此,最先调用cluster模块的是master进程,然后才是子进程。多进程端口问题运行上面的app.js,成功启动1个Master进程和2个Worker进程。因为只有一个8000端口,所以我们需要看看它在监听哪些进程。lsof-i:8000node8551qianduanyiguozhu23uIPv60xb5e3cbb6deb4d65d0t0TCP*:irdmi(LISTEN)现在我们知道8000端口并没有被所有进程监听,只有Master进程监听。我们再来看一条信息。ps-ef|grep8551501855285510下午5:53??0:00.10501855385510下午5:53??0:00.10这清楚地表明了Worker和Master之间的关系。master是通过cluster.fork()方法创建的,如何实现进程间端口共享?前面的例子中,多个woker中创建的server监听同一个8000端口,一般来说,如果多个进程监听同一个端口,系统会报错。为什么我们的例子没问题?秘密在于在net模块中,server.listen()方法被特殊对待。根据当前进程是master进程还是worker进程:master进程:正常监听该端口的请求。(无需特殊处理)工作进程:创建服务器实例。然后使用send方法向master进程发送消息,让master进程也创建一个server实例,并在这个端口上监听请求。当请求进来时,主进程将请求转发给工作进程的服务器实例。总结就是:端口只会被主进程绑定监听一次。master进程监听特定端口,通过负载均衡技术将客户端请求转发给各个worker进程。负载均衡原理Node.js集群模块采用master-subprocess的方式,那么它是如何进行负载均衡处理的呢?这里主要涉及两个模块。round_robin_handle.js(非Windows平台应用模式),这是一种round-robin处理模式,即round-robin调度分发给空闲的子进程,处理完成后返回到空闲的worker池中.这里需要注意的是,如果绑定了子进程会被复用,如果没有绑定会重新判断,这里可以通过上面的app.js代码测试一下,用浏览器访问一下,你会发现每次调用子进程ID不变。shared_handle.js(Windows平台应用模式),通过传递文件描述符、端口等信息给子进程,子进程通过这些信息创建对应的SocketHandle/ServerHandle,然后绑定对应的端口并监听和处理请求.