您好,登錄后才能下訂單哦!
NodeJs是一個單進程的語言,不能像Java那樣可以創建多線程來并發執行。當然在大部分情況下,NodeJs是不需要并發執行的,因為它是事件驅動性永不阻塞。但單進程也有個問題就是不能充分利用CPU的多核機制,根據前人的經驗,可以通過創建多個進程來充分利用CPU多核,并且Node通過了child_process模塊來創建完成多進程的操作。
child_process模塊給予node任意創建子進程的能力,node官方文檔對于child_proces模塊給出了四種方法,映射到操作系統其實都是創建子進程。但對于開發者而已,這幾種方法的api有點不同
child_process.exec(command[, options][, callback]) 啟動子進程來執行shell命令,可以通過回調參數來獲取腳本shell執行結果
child_process.execfile(file[, args][, options][, callback]) 與exec類型不同的是,它執行的不是shell命令而是一個可執行文件
child_process.spawn(command[, args][, options])僅僅執行一個shell命令,不需要獲取執行結果
child_process.fork(modulePath[, args][, options])可以用node執行的.js文件,也不需要獲取執行結果。fork出來的子進程一定是node進程
exec()與execfile()在創建的時候可以指定timeout屬性設置超時時間,一旦超時會被殺死
如果使用execfile()執行可執行文件,那么頭部一定是#!/usr/bin/env node
進程間通信
node 與 子進程之間的通信是使用IPC管道機制完成。如果子進程也是node進程(使用fork),則可以使用監聽message事件和使用send()來通信。
main.js
var cp = require('child_process'); //只有使用fork才可以使用message事件和send()方法 var n = cp.fork('./child.js'); n.on('message',function(m){ console.log(m); }) n.send({"message":"hello"});
child.js
var cp = require('child_process'); process.on('message',function(m){ console.log(m); }) process.send({"message":"hello I am child"})
父子進程之間會創建IPC通道,message事件和send()便利用IPC通道通信.
句柄傳遞
學會如何創建子進程后,我們創建一個HTTP服務并啟動多個進程來共同做到充分利用CPU多核。
worker.js
var http = require('http'); http.createServer(function(req,res){ res.end('Hello,World'); //監聽隨機端口 }).listen(Math.round((1+Math.random())*1000),'127.0.0.1');
main.js
var fork = require('child_process').fork; var cpus = require('os').cpus(); for(var i=0;i<cpus.length;i++){ fork('./worker.js'); }
上述代碼會根據你的cpu核數來創建對應數量的fork進程,每個進程監聽一個隨機端口來提供HTTP服務。
上述就完成了一個典型的Master-Worker主從復制模式。在分布式應用中用于并行處理業務,具備良好的收縮性和穩定性。這里需要注意,fork一個進程代價是昂貴的,node單進程事件驅動具有很好的性能。此例的多個fork進程是為了充分利用CPU的核,并非解決并發問題.
上述示例有個不太好的地方就是占有了太多端口,那么能不能對于多個子進程全部使用同一個端口從而對外提供http服務也只是使用這一個端口。嘗試將上述的端口隨機數改為8080,啟動會發現拋出如下異常。
events.js:72 throw er;//Unhandled 'error' event Error:listen EADDRINUSE XXXX
拋出端口被占有的異常,這意味著只有一個worker.js才能監聽8080端口,而其余的會拋出異常。
如果要解決對外提供一個端口的問題,可以參考nginx反向代理的做法。對于Master進程使用80端口對外提供服務,而對于fork的進程則使用隨機端口,Master進程接受到請求就將其轉發到fork進程中
對于剛剛所說的代理模式,由于進程每收到一個連接會使用掉一個文件描述符,因此代理模式中客戶端連接到代理進程,代理進程再去連接fork進程會使用掉兩個文件描述符,OS中文件描述符是有限的,為了解決這個問題,node引入進程間發送句柄的功能。
在node的IPC進程通訊API中,send(message,[sendHandle])的第二個參數就是句柄。
句柄就是一種標識資源的引用,它的內部包含了指向對象的文件描述符。句柄可以用來描述一個socket對象,一個UDP套接子,一個管道主進程向工作進程發送句柄意味著當主進程接收到客戶端的socket請求后則直接將這個socket發送給工作進程,而不需要再與工作進程建立socket連接,則文件描述符的浪費即可解決。我們來看示例代碼:
main.js
var cp = require('child_process'); var child = cp.fork('./child.js'); var server = require('net').createServer(); //監聽客戶端的連接 server.on('connection',function(socket){ socket.end('handled by parent'); }); //啟動監聽8080端口 server.listen(8080,function(){ //給子進程發送TCP服務器(句柄) child.send('server',server); });
child.js
process.on('message',function(m,server){ if(m==='server'){ server.on('connection',function(socket){ socket.end('handle by child'); }); } });
使用telnet或curl都可以測試:
wang@wang ~/code/nodeStudy $ curl 192.168.10.104:8080
handled by parent
wang@wang ~/code/nodeStudy $ curl 192.168.10.104:8080
handle by child
wang@wang ~/code/nodeStudy $ curl 192.168.10.104:8080
handled by parent
wang@wang ~/code/nodeStudy $ curl 192.168.10.104:8080
handled by parent
測試結果是每次對于客戶端的連接,有可能父進程處理也有可能被子進程處理。現在我們嘗試僅提供http服務,并且為了讓父進程更加輕量,僅讓父進程傳遞句柄給子進程而不做請求處理:
main.js
var cp = require('child_process'); var child1 = cp.fork('./child.js'); var child2 = cp.fork('./child.js'); var child3 = cp.fork('./child.js'); var child4 = cp.fork('./child.js'); var server = require('net').createServer(); //父進程將接收到的請求分發給子進程 server.listen(8080,function(){ child1.send('server',server); child2.send('server',server); child3.send('server',server); child4.send('server',server); //發送完句柄后關閉監聽 server.close(); });
child.js
var http = require('http'); var serverInChild = http.createServer(function(req,res){ res.end('I am child.Id:'+process.pid); }); //子進程收到父進程傳遞的句柄(即客戶端與服務器的socket連接對象) process.on('message',function(m,serverInParent){ if(m==='server'){ //處理與客戶端的連接 serverInParent.on('connection',function(socket){ //交給http服務來處理 serverInChild.emit('connection',socket); }); } });
當運行上述代碼,此時查看8080端口占有會有如下結果:
wang@wang ~/code/nodeStudy $ lsof -i:8080
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
node 5120 wang 11u IPv6 44561 0t0 TCP *:http-alt (LISTEN)
node 5126 wang 11u IPv6 44561 0t0 TCP *:http-alt (LISTEN)
node 5127 wang 11u IPv6 44561 0t0 TCP *:http-alt (LISTEN)
node 5133 wang 11u IPv6 44561 0t0 TCP *:http-alt (LISTEN)
運行curl查看結果:
wang@wang ~/code/nodeStudy $ curl 192.168.10.104:8080
I am child.Id:5127
wang@wang ~/code/nodeStudy $ curl 192.168.10.104:8080
I am child.Id:5133
wang@wang ~/code/nodeStudy $ curl 192.168.10.104:8080
I am child.Id:5120
wang@wang ~/code/nodeStudy $ curl 192.168.10.104:8080
I am child.Id:5126
wang@wang ~/code/nodeStudy $ curl 192.168.10.104:8080
I am child.Id:5133
wang@wang ~/code/nodeStudy $ curl 192.168.10.104:8080
I am child.Id:5126
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。