摘要:進(jìn)程間通信的目的是為了讓不同的進(jìn)程能夠互相訪問資源,并進(jìn)程協(xié)調(diào)工作。這個過程的示意圖如下端口共同監(jiān)聽集群穩(wěn)定之路進(jìn)程事件自動重啟負(fù)載均衡狀態(tài)共享模塊工作原理事件二測試單元測試性能測試三產(chǎn)品化項目工程化部署流程性能日志監(jiān)控報警穩(wěn)定性異構(gòu)共存
內(nèi)容
9.玩轉(zhuǎn)進(jìn)程一、玩轉(zhuǎn)進(jìn)程
10.測試
11.產(chǎn)品化
node的單線程只不過是js層面的單線程,是基于V8引擎的單線程,因為,V8的緣故,前后端的js執(zhí)行模型基本上是類似的,但是node的內(nèi)核機(jī)制依然是通過libuv調(diào)用epoll或者IOCP的多線程機(jī)制。換句話說,node從嚴(yán)格意義上講,并非是真正的單線程架構(gòu),node內(nèi)核自身有一定的IO線程和IO線程池,通過libuv的調(diào)度,直接使用了操作系統(tǒng)層面的多線程。node的開發(fā)者,可以通過擴(kuò)展c/c++模塊來直接操縱多線程來提高效率。不過,單線程帶來的好處是程序狀態(tài)單一,沒有鎖、線程同步、線程上下文切換等問題。但是單線程的程序,并非是完美的。現(xiàn)在的服務(wù)器很多都是多cpu,多cpu核心的,一個node實例只能利用一個cpu核心,那么其他的cpu核心不就浪費(fèi)了嗎?并且,單線程的容錯也很弱,一旦拋出了沒有捕獲的異常,必將引起整個程序的崩潰,那這樣的程序必然是非常脆弱的,這樣的服務(wù)器端語言又有什么價值呢?
兩個問題:
如何讓node充分利用多核cpu服務(wù)器?
如何保證node進(jìn)程的健壯性和穩(wěn)定性?
1.服務(wù)模型的變遷經(jīng)歷了同步(qps為1/n)、復(fù)制進(jìn)程(預(yù)先賦值一定數(shù)量的進(jìn)程,prefork,但是,一旦用超了,還是跟同步的服務(wù)器一樣,qps為m/n)、多線程(qps為M*L/N,這種模型,當(dāng)并發(fā)上萬后,內(nèi)存耗用的問題將會暴露出來也就是C10k問題,apache就是采用了這樣的多線程、多進(jìn)程架構(gòu))和事件驅(qū)動等幾個不同的模型。
2.多進(jìn)程架構(gòu)面對單進(jìn)程單線程對多核使用不足的問題,前人的經(jīng)驗是啟動多個進(jìn)程,理想狀態(tài)下,每個進(jìn)程各自利用一個cpu,以此實現(xiàn)多核cpu的利用。node提供了child_process模塊,并提供了child_process.fork()函數(shù)來實現(xiàn)進(jìn)程的復(fù)制。
//node worker.js var http = require("http"); http.createServer(function (req, res) { res.writeHead(200, {"Content-Type": "text/plain"}); res.end("Hello World "); }).listen(Math.round((1 + Math.random()) * 1000), "127.0.0.1"); //node master.js var fork = require("child_process").fork; var cpus = require("os").cpus(); for (var i = 0; i < cpus.length; i++) { fork("./worker.js"); }
這兩段代碼會根據(jù)當(dāng)前機(jī)器上的cpu數(shù)量,復(fù)制出對應(yīng)node進(jìn)程數(shù),在*nix下,可以通過ps aux | grep worker.js查看到進(jìn)程的數(shù)量。
這就是主從架構(gòu)了,在這里存在兩個進(jìn)程,master是主進(jìn)程、worker是工作進(jìn)程。這是典型的分布式架構(gòu)用于并行業(yè)務(wù)處理的模式,具有較好的可伸縮性和穩(wěn)定性。主進(jìn)程不負(fù)責(zé)具體業(yè)務(wù)處理,只負(fù)責(zé)調(diào)度和管理工作進(jìn)程,因此主進(jìn)程是相對于穩(wěn)定和簡單的,工作進(jìn)程負(fù)責(zé)具體的業(yè)務(wù)處理,因為,業(yè)務(wù)多種多樣,所以,工作進(jìn)程的穩(wěn)定性,是我們需要考慮的。
通過fork復(fù)制的進(jìn)程都是獨(dú)立的,每個進(jìn)程都有著獨(dú)立而全新的v8實例,因此,需要至少30毫秒的啟動時間和10mb左右的內(nèi)存,但是,我們要記得fork進(jìn)程是昂貴的,好在node在事件驅(qū)動的方式上,實現(xiàn)了單線程解決大并發(fā)的問題,這里啟動多個進(jìn)程只是為了充分將cpu資源利用起來,而不是為了解決并發(fā)的問題。
1).創(chuàng)建子進(jìn)程
child_process模塊給予了node隨意創(chuàng)建子進(jìn)程(child_process)的能力,它提供了4個方法用于創(chuàng)建子進(jìn)程。
spawn():啟動一個子進(jìn)程來執(zhí)行命令
exec():啟動一個子進(jìn)程來執(zhí)行命令,與spawn()不同的是使用了不同的接口,它有一個回調(diào)函數(shù)獲知子進(jìn)程的狀況。
execFile():啟動一個子進(jìn)程來執(zhí)行可執(zhí)行文件
fork():與spawn()類似,不同點(diǎn)在于,它創(chuàng)建node的子進(jìn)程只需要指定要執(zhí)行的js文件模塊即可。
spawn()與exec()、execFile()不同的是,后兩者創(chuàng)建時可指定timeout屬性,設(shè)置超時時間,一旦創(chuàng)建的進(jìn)程運(yùn)行超過設(shè)定的時間進(jìn)程將會被殺死。
exec()與execFile()不同的是,exec()適合執(zhí)行已有的命令,execFile()適合執(zhí)行文件。這里我們一node worker.js為例,來分別實現(xiàn)上述的4中方法
var cp = require("child_process"); cp.spawn("node", ["worker.js"]); cp.exec("node worker.js", function (err, stdout, stderr) { // some code }); cp.execFile("worker.js", function (err, stdout, stderr) { // some code }); cp.fork("./worker.js");
以上四個方法在創(chuàng)建子進(jìn)程后,均會返回子進(jìn)程對象,他們的差別如下:
這里的可執(zhí)行文件是指直接可以執(zhí)行的,也就是*.exe或者.sh,如果是js文件,通過execFile()運(yùn)行,那么這個文件的首行必須添加環(huán)境變量:#!/usr/bin/env node,盡管4種創(chuàng)建子進(jìn)程的方式存在差別,但是事實上后面3種方法都是spawn()的延伸應(yīng)用。
2)進(jìn)程間通信
主線程與工作線程之間通過onmessage()和postMessage()進(jìn)程通信,子進(jìn)程對象則由send()方法實現(xiàn)主進(jìn)程向子進(jìn)程發(fā)送數(shù)據(jù),message事件實現(xiàn)收聽子進(jìn)程發(fā)來的數(shù)據(jù),與api在一定程度上相似。通過消息傳遞,而不是共享或直接操縱相關(guān)資源,這是較為輕量和無依賴的做法。
// parent.js var cp = require("child_process"); var n = cp.fork(__dirname + "/sub.js"); n.on("message", function (m) { console.log("PARENT got message:", m); }); n.send({ hello: "world" }); // sub.js process.on("message", function (m) { console.log("CHILD got message:", m); }); process.send({ foo: "bar" });
通過fork()或其他api創(chuàng)建子進(jìn)程后,為了實現(xiàn)父子進(jìn)程之間的通信,父進(jìn)程與子進(jìn)程之間將會創(chuàng)建IPC通道,通過IPC通道,父子進(jìn)程之間才能通過message和send()傳遞消息。
進(jìn)程間通信原理
PC的全稱是Inter-Process Communication,即進(jìn)程間通信。進(jìn)程間通信的目的是為了讓不同的進(jìn)程能夠互相訪問資源,并進(jìn)程協(xié)調(diào)工作。實現(xiàn)進(jìn)程間通信的技術(shù)有很多,如命名管道、匿名管道、socket、信號量、共享內(nèi)存、消息隊列、Domain Socket等,node中實現(xiàn)IPC通道的是管道技術(shù)(pipe)。
在node中管道是個抽象層面的稱呼,具體細(xì)節(jié)實現(xiàn)由libuv提供,在win下是命名管道(named pipe)實現(xiàn),在*nix下,采用unix Domain Socket來實現(xiàn)。
但是,具體在應(yīng)用層面只是簡單的message事件和send()方法,接口十分簡潔和消息化。
父進(jìn)程在實際創(chuàng)建子進(jìn)程前,會創(chuàng)建IPC通道并監(jiān)聽它,然后才真正創(chuàng)建出子進(jìn)程,并通過環(huán)境變量(NODE_CHANNEL_FD)告訴子進(jìn)程這個IPC通信的文件描述符。子進(jìn)程在啟動的過程中,根據(jù)文件描述符去連接這個已存在的IPC通道,從而完成父子進(jìn)程之間的連接。
建立連接之后的父子進(jìn)程就可以自由的通信了,由于IPC通道是用命名管道或者Domain Socket創(chuàng)建的,他們與網(wǎng)絡(luò)socket的行為比較類似,屬于雙向通道。不同的是他們在系統(tǒng)內(nèi)核中就完了進(jìn)程間的通信,而不經(jīng)過實際的網(wǎng)絡(luò)層,非常高效。在node中,IPC通道被抽象為stream對象,在調(diào)用send()時發(fā)送數(shù)據(jù)(類似于write()),接收到的消息會通過message事件(類似于data)觸發(fā)給應(yīng)用層。
注意:只有啟動的子進(jìn)程是node進(jìn)程是,子進(jìn)程才會根據(jù)環(huán)境變量去連接IPC通道,對于其他類型的子進(jìn)程則無法自動實現(xiàn)進(jìn)程間通信,需要讓其他進(jìn)程也按照約定去連接這個已經(jīng)創(chuàng)建好的IPC通道才行。
3)句柄傳遞
進(jìn)程間發(fā)送句柄的功能,send()方法除了能夠通過IPC發(fā)送數(shù)據(jù)外還能發(fā)送句柄,第二個可選參數(shù)就是句柄:
child.send(message, [sendHandle])
句柄是一種可以用來標(biāo)識資源的引用,它的內(nèi)部包含了指向?qū)ο蟮奈募枋龇?。因此,句柄可以用來?biāo)識一個服務(wù)端的socket對象、一個客戶端的socket對象、一個udp套接字、一個管道等。
這個句柄就解決了一個問題,我們可以去掉代理方案,在主進(jìn)程接收到socket請求后,將這個socket直接發(fā)送給工作進(jìn)程,而不重新與工作進(jìn)程之間建立新的socket連接轉(zhuǎn)發(fā)數(shù)據(jù)。我們來看一下代碼實現(xiàn):
主進(jìn)程發(fā)送完句柄,并關(guān)閉監(jiān)聽之后,就變成了如下結(jié)構(gòu):
這樣,就可以實現(xiàn)多個子進(jìn)程可以同時監(jiān)聽相同端口,再沒有EADDRINUSE的異常發(fā)生。
1.句柄發(fā)送與還原
子進(jìn)程對象send()方法可以發(fā)送的句柄類型包括如下幾種:
net.socket,tcp套接字
net.Server,tcp服務(wù)器,任意建立在tcp服務(wù)上的應(yīng)用層服務(wù)都可以享受到它帶來的好處。
net.Native,c++層面的tcp套接字或IPC管道。
dgram.socket,UDP套接字
dgram.Native,C++層面的UDP套接字
send()方法在將消息發(fā)送到IPC管道前,將消息組裝成兩個對象,一個參數(shù)是handle,另一個是message
//message參數(shù) { cmd: "NODE_HANDLE", type: "net.Server", msg: message }
發(fā)送到IPC管道中的實際上是我們要發(fā)送的句柄文件描述符,文件描述符實際上是一個整數(shù)值,這個message對象在寫入到IPC通道時,也會通過JSON.stringify()進(jìn)行序列化,所以最終發(fā)送到IPC通道中的信息都是字符串,send()方法能發(fā)送消息和句柄并不意味著它能發(fā)送任意對象。
連接了IPC通道的子進(jìn)程可以讀取到父進(jìn)程發(fā)來的消息,將字符串通過JSON.parse()解析還原為對象后,才出發(fā)message事件將消息體傳遞給應(yīng)用層使用,在這個過程中,消息對象還要被進(jìn)行過濾處理,message.cmd的值如果以NODE_為前綴,它將響應(yīng)一個內(nèi)部事件internalMessage
如果message.cmd值為NODE_HANDLE,它將取出message.type的值和得到的文件描述符一起還原出一個對應(yīng)的對象。這個過程的示意圖如下:
2.端口共同監(jiān)聽
3.集群穩(wěn)定之路1)進(jìn)程事件
2)自動重啟
3)負(fù)載均衡
4)狀態(tài)共享
1)Cluster工作原理
2)Cluster事件
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/103967.html
摘要:從社區(qū)和過往的經(jīng)驗而言異步編程的難題已經(jīng)基本解決無論是通過事件還是通過模式或者流程控制庫。本章主要介紹了主流的幾種異步編程解決方案這是目前中主要使用的方案。最后因為人們總是習(xí)慣性地以線性的方式進(jìn)行思考以致異步編程相對較為難以掌握。 前言 如果你想要深入學(xué)習(xí)Node,那你不能錯過《深入淺出Node.js》這本書,它從不同的視角介紹了 Node 內(nèi)在的特點(diǎn)和結(jié)構(gòu)。由首章Node 介紹為索引...
摘要:特意對前端學(xué)習(xí)資源做一個匯總,方便自己學(xué)習(xí)查閱參考,和好友們共同進(jìn)步。 特意對前端學(xué)習(xí)資源做一個匯總,方便自己學(xué)習(xí)查閱參考,和好友們共同進(jìn)步。 本以為自己收藏的站點(diǎn)多,可以很快搞定,沒想到一入?yún)R總深似海。還有很多不足&遺漏的地方,歡迎補(bǔ)充。有錯誤的地方,還請斧正... 托管: welcome to git,歡迎交流,感謝star 有好友反應(yīng)和斧正,會及時更新,平時業(yè)務(wù)工作時也會不定期更...
摘要:謹(jǐn)記,請勿犯這樣的錯誤。由于在之前的教程中,積累了堅實的基礎(chǔ)。其實,這是有緣由的其復(fù)雜度在早期的學(xué)習(xí)過程中,將會帶來災(zāi)難性的影響。該如何應(yīng)對對于來說,雖然有大量的學(xué)習(xí)計劃需要采取,且有大量的東西需要學(xué)習(xí)。 前言倘若你正在建造一間房子,那么為了能快點(diǎn)完成,你是否會跳過建造過程中的部分步驟?如在具體建設(shè)前先鋪設(shè)好部分石頭?或直接在一塊裸露的土地上先建立起墻面? 又假如你是在堆砌一個結(jié)婚蛋糕...
閱讀 3486·2023-04-26 02:48
閱讀 1475·2021-10-11 10:57
閱讀 2502·2021-09-23 11:35
閱讀 1210·2021-09-06 15:02
閱讀 3310·2019-08-30 15:54
閱讀 1626·2019-08-30 15:44
閱讀 893·2019-08-30 15:44
閱讀 1000·2019-08-30 12:52