我们启动一个服务,运行一个实例,也就是开启一个服务进程。在Node.js中,通过nodeapp.js启动一个服务进程。多进程就是进程的复制(fork),fork出来的每个进程都有自己独立的空间地址和数据栈。一个进程不能访问另一个进程中定义的变量和数据结构。只有建立了IPC通信,进程之间才能共享数据。child_processnode.js可以通过以下四种方式创建子进程:{spawn}=require("child_process");//创建文件spawn("touch",["index.js"]);spawn()会返回child-process子进程实例:const{spawn}=require("child_process");//cwd指定子进程的工作目录,默认当前目录constchild=spawn("ls",["-l"],{cwd:__dirname});//输出进程信息child.stdout.pipe(process.stdout);console.log(process.pid,child.pid);子进程也是基于事件机制(EventEmitterAPI),提供了一些事件:exit:子进程退出时触发,可以知道进程退出状态(代码和信号)disconnect:父进程调用child时触发。disconnect()错误:子进程创建失败,或者被杀死时触发close:子进程的stdio流(标准输入输出流)关闭时触发发送消息时触发。父子进程消息通信close和exit的区别主要体现在多个??进程共享同一个stdio流的场景。退出进程并不意味着stdio流关闭,子进程具有可读性。流的特性,使用可读流来实现find。-类型f|wc-l,递归统计当前目录的文件数:const{spawn}=require('child_process');constfind=spawn('find',['.','-type','f']);constwc=spawn('wc',['-l']);查找.stdout.pipe(wc.stdin);wc.stdout.on('data',(data)=>{console.log(`文件数${data}`);});execspawn()和exec()的区别在于,exec()不是基于流的,exec()会将传入命令的执行结果暂时存放在缓冲区中,然后整体传递给回调函数spawn()会默认不创建shell来执行命令(性能会稍微好一些),而exec()方法执行会先创建一个shell,所以exec()方法中可以传入任何shell脚本。const{exec}=require("child_process");exec("node-v",(error,stdout,stderr)=>{if(error)console.log(error);console.log(stdout)})exec()方法存在安全风险,因为它可以传入任何shell脚本。spawn()方法默认不会创建shell来执行传入的命令(所以性能稍微好一点),但是可以通过参数实现:const{spawn}=require('child_process');constchild=spawn('node-v',{shell:true});child.stdout.pipe(process.stdout);这种方式的好处是不仅可以支持shell语法,还可以通过流IO进行标准输入输出。execFileconst{execFile}=require("child_process");execFile("node",["-v"],(error,stdout,stderr)=>{console.log({error,stdout,stderr})console.log(stdout)})通过可执行文件路径执行:const{execFile}=require("child_process");execFile("/Users/.nvm/versions/node/v12.1.0/bin/node",["-v"],(error,stdout,stderr)=>{console.log({error,stdout,stderr})console.log(stdout)})fork通过fork()方法可以创建一个Node进程,父子进程可以相互通信//master.jsconst{fork}=require("child_process");constworker=fork("worker.js");worker.on("message",(msg)=>{console.log(`fromworder:${msg}`)});worker.send("thisismaster");//worker.jsprocess.on("message",(msg)=>{console.log("worker",msg)});process.send("thisisworker");使用fork()可用于处理需要大量计算且耗时较长的任务:constlongComputation=()=>{letsum=0;for(leti=0;i<1e10;i++){sum+=i;};返回总和;};将longComputation方法拆分成子进程,这样主进程的事件循环就不会被耗时计算阻塞:consthttp=require('http');const{fork}=require('child_process');constserver=http.createServer();server.on('request',(req,res)=>{if(req.url==='/compute'){//会计算一个大的Task,拆分成子进程处理constcompute=fork('compute.js');compute.send('start');compute.on('message',sum=>{//接收子进程任务后,returnres.end(`Sumis${sum}`);});}else{res.end('Ok')}});server.listen(3000);进程间通信IPC每个进程都有自己不同的用户地址空间,任何一个进程的全局变量在另一个进程中是看不到的,所以进程间的数据交换必须经过内核,在内核中开辟一个缓冲区,进程1将数据从用户空间复制到内核缓冲区,进程2再次从内核缓冲区读取数据,内核提供的这种机制称为进程间通信(IPC,InterProcessCommunication)。进程可以使用内置的IPC机制与父进程通信:接收事件进程。on('message')向子进程发送信息master.send()子进程:收到一个事件process.on('message')向父进程发送信息process.send()forkmulti-processmulti-processinnodejs是多进程+单线程模式//master.js.process.title='node-master'constnet=require("net");const{fork}=require("child_process");consthandle=net._createServerHandle("127.0.0.1",3000);for(leti=0;i<4;i++){fork("./worker.js").send({},handle);}//工人。jsprocess.title='worker-master';const网络=要求ire("net");process.on("message",(msg,handle)=>start(handle));constbuf="hellonodejs";constres=["HTTP/1.1200ok","内容-length:"+buf.length].join("\r\n")+"\r\n\r\n"+buf;functionstart(server){server.listen();让数=0;server.onconnection=function(err,handle){num++;console.log(`worker${process.pid}num${num}`);让socket=newnet.Socket({handle});socket.readable=socket.writable=truesocket.end(res);}}运行nodemaster.js,这里可以使用测试工具siege:siege-c20-r10http://localhost:3000-c并发数,并发数为20人-r为重复次数,重复10次。这个创建进程的特点是:在一个服务上同时启动多个进程,每个进程运行相同的代码(start方法)。多个进程可以同时监听一个端口(3000)。master不知道每个请求交给哪个worker。我们希望master能掌控全局,把请求分配给worker。我们做如下转换://master.jsprocess.title='node-master'constnet=require("net");const{fork}=require("child_process");//定义workers变量来保存子进程workerletworkers=[];for(leti=0;i<4;i++){workers.push(fork("./worker.js"));}consthandle=net._createServerHandle("0.0.0.0",3000)handle.listen();//主控请求handle.onconnection=function(err,handle){letworker=workers.pop();//将请求传递给子进程worker.send({},handle);workers.unshift(worker);}//worker.jsprocess.title='worker-master';constnet=require("net")process.on("message",(msg,handle)=>start(handle))constbuf="hellonodejs"constres=["HTTP/1.1200ok","content-length:"+buf.length].join("\r\n")+"\r\n\r\n"+buffunctionstart(handle){console.log(`getaconnectiononworker,pid=%d`,process.pid)letsocket=newnet.Socket({handle})socket.readable=socket.writable=truesocket.end(res)}Cluster多进程Node.js官方提供的Cluster模块不仅使得充分利用机器CPU核心开箱即用的方案,也有助于增加Node进程能力的可用性,Cluster模块是对多进程服务能力的封装//master.jsconstcluster=require("cluster");constnumCPUS=require("os").cpus().length;if(cluster.isMaster){console.log(`masterstart...`)for(leti=0;i
