当前位置: 首页 > 后端技术 > Node.js

node.js多进程架构

时间:2023-04-03 18:07:12 Node.js

node.js是单进程应用,要充分利用多核cpu的性能,就需要使用多进程架构。作为web服务器,多个进程不能创建不同的socket文件描述符来接受网络请求。有经验的同学都知道,如果端口被占用,运行另外一个服务监听该端口,会报EADDRINUSE异常。那么问题来了,多进程架构是如何解决这个问题的呢?我们将多进程架构设计为典型的master-worker架构,一个master,多个worker。master-worker架构如下图所示:我们可以在master进程中代理accept请求,分配给worker处理。但是client进程连接master进程,master进程连接worker进程需要使用两个文件描述符,这样会浪费两倍的文件描述符。因此,让工作人员接受请求将是一个更好的解决方案。master先创建一个server监听端口,然后通过进程间通信将socket文件描述符传递给所有worker进程,worker进程将传递过来的socket文件描述符封装到一个server中(好像是发送一个server对象给另一个进程其实就是封装了对应的handle,通过JSON.stringify()序列化后发送,接收端进程恢复对应的handle。)那么,还有一个问题,如果其中一个worker进程异常退出怎么办?这时候worker进程应该通知master进程,然后master进程再fork一个worker进程。先上master代码:复制代码1"usestrict"23constfork=require('child_process').fork;4constcpus=require('os').cpus();5letserver=require('net').createServer((socket)=>{6//'connection'监听器7socket.end('Handledbymastern');8console.error('Handledbymastern');//不应该在masteracceptrequest9});101112server.listen(8001);1314letworkers={};1516functioncreateWorker(ser){17letworker=fork('./worker.js');1819worker.on('message',function(msg,handle){20//收到子进程的通知后需要创建一个新的worker(在子进程之前通知父进程exits)21if(msg==='new_worker'){22letser=handle;23createWorker(ser);24//close25ser.close();26}27})282930worker.on('exit',function(code,signal){31deleteworkers[worker.pid];32});3334//处理转发35letresult=worker.send('server',ser,(err)=>{err&&console.error(err)});36console.info('发送服务器到childresult:',result);37workers[worker.pid]=worker;38}3940for(leti=0;i子进程自行退出时的退出代码。49signal子进程终止的信号。50*/51process.on('exit',function(code,signal){52console.log(masterexit,code:${code},signal:${signal});53for(letpidinworkers){54workers[pid].kill();55}56})5758process.on('uncaughtException',function(error){59console.error('master|uncaughtException,error:',error);60进程.exit(1);61})6263//一些常用的退出信号处理:64//killpid默认为SIGTERM信号65//consolectrl-c为SIGINT信号66constkillSignalList=['SIGTERM','SIGINT'];67killSignalList.forEach((SIGNAL)=>{68process.on(SIGNAL,function(){69console.log(${SIGNAL}signal);70process.exit(1);71})72})复杂的master进程根据CPU核数fork出相应数量的worker进程。fork成功后,立即将serverhandle发送给worker进程。在fork所有工作进程后,它关闭服务器并且不再接收请求。master进程在退出前会调用worker。kill()方法杀死所有工作进程。worker代码如下:复制代码1consthttp=require('http');234constserver=http.createServer(function(req,res){5//'request'监听器6res.end('handledbyworkern');7//thrownewError('error');8})910letworker;11process.on('message',function(msg,handle){12if(msg==='server'){13worker=handle;14worker.on('connection',function(socket){15server.emit('connection',socket);16})17}1819})202122process.on('uncaughtException',function(err){23console.error('uncaughtExceptionerr:',err.message,',worker进程将重启');24//通知master创建一个新的worker25process.send('new_worker',worker);26//停止接受新连接27worker.close(function(){28//现有连接全部断开后,退出进程29process.exit(1);30});31});复制代码worker进程有一个详细的处理地方:异常退出前,先通知masterprocess来创建一个新的worker,然后等待所有现有连接断开后再退出process。关于进程间的句柄发送函数,有兴趣的同学可以多了解下。子进程对象send(message,[sendHandle])方法可以发送的句柄类型有:net.Socket,TCPsocket。net.Server,TCP服务器,任何基于TCP服务的应用层服务都可以享受到它带来的好处。C++级别的net.Native、TCP套接字或IPC通道。dgram.Socket,UDP套接字。dgram.Native,C++级别的UDP套接字。多个worker进程监听同一个socket,会造成拥挤现象。当有请求到来时,CPU会唤醒所有的工作进程。最终只有一个进程接受请求,其他进程接受请求失败,这样就产生了一些不必要的开销。如何避免惊群现象,我会另写一篇文章详细讲。