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