<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          淺析 NodeJS 多進(jìn)程和集群

          共 8861字,需瀏覽 18分鐘

           ·

          2021-06-14 14:50

          進(jìn)程

          進(jìn)程是指在系統(tǒng)中正在運行的一個應(yīng)用程序。

          當(dāng)我們打開活動監(jiān)視器或者文件資源管理器時,可以看到每一個正在運行的進(jìn)程:

          多進(jìn)程

          復(fù)制進(jìn)程

          NodeJS 提供了 child_process 模塊,并且提供了 child_process.fork() 函數(shù)供我們復(fù)制進(jìn)程。

          舉個??

          在一個目錄下新建 worker.js 和 master.js 兩個文件:

          worker.js

          const http = require('http');

          http.createServer((req, res) => {
            res.writeHead(200, {'Content-Type''text/plain'});
            res.end('Hello NodeJS!\n');
          }).listen(Math.round((1 + Math.random()) * 2000), '127.0.0.1');

          master.js

          const { fork } = require('child_process');
          const { cpus } = require('os');

          cpus().map(() => {
            fork('./worker.js');
          });

          通過 node master.js 啟動 master.js,然后通過 ps aux | grep worker.js 查看進(jìn)程的數(shù)量,我們可以發(fā)現(xiàn),理想狀況下,進(jìn)程的數(shù)量等于 CPU 的核心數(shù),每個進(jìn)程各自利用一個 CPU,實現(xiàn)多核 CPU 的利用:


          這是經(jīng)典的 Master-Worker 模式(主從模式)

          實際上,fork 進(jìn)程是昂貴的,復(fù)制進(jìn)程的目的是充分利用 CPU 資源,所以 NodeJS 在單線程上使用了事件驅(qū)動的方式來解決高并發(fā)的問題。

          子進(jìn)程的創(chuàng)建

          child_process 模塊提供了四個方法創(chuàng)建子進(jìn)程:

          • child_process.spawn(command, args)
          • child_process.exec(command, options)
          • child_process.execFile(file, args[, callback])
          • child_process.fork(modulePath, args)

          對比:


          后三種方法都是 spawn() 的延伸。

          進(jìn)程間的通信

          在 NodeJS 中,子進(jìn)程對象使用 send() 方法實現(xiàn)主進(jìn)程向子進(jìn)程發(fā)送數(shù)據(jù),message 事件實現(xiàn)主進(jìn)程收聽由子進(jìn)程發(fā)來的數(shù)據(jù)。

          舉個??

          在一個目錄下新建 parent.js 和 child.js 兩個文件:

          parent.js

          const { fork } = require('child_process');
          const sender = fork(__dirname + '/child.js');

          sender.on('message', msg => {
            console.log('主進(jìn)程收到子進(jìn)程的信息:', msg);
          });

          sender.send('Hey! 子進(jìn)程');

          child.js

          process.on('message', msg => {
            console.log('子進(jìn)程收到來自主進(jìn)程的信息:', msg);
          });

          process.send('Hey! 主進(jìn)程');

          當(dāng)我們執(zhí)行 node parent.js 時,會出現(xiàn)如下圖所示:


          這樣我們就實現(xiàn)了一個最基本的進(jìn)程間通信。

          IPC

          IPC 即進(jìn)程間通信,可以讓不同進(jìn)程之間能夠相互訪問資源并協(xié)調(diào)工作。

          實際上,父進(jìn)程會在創(chuàng)建子進(jìn)程之前,會先創(chuàng)建 IPC 通道并監(jiān)聽這個 IPC,然后再創(chuàng)建子進(jìn)程,通過環(huán)境變量(NODE_CHANNEL_FD)告訴子進(jìn)程和 IPC 通道相關(guān)的文件描述符,子進(jìn)程啟動的時候根據(jù)文件描述符連接 IPC 通道,從而和父進(jìn)程建立連接。


          句柄傳遞

          句柄是一種可以用來標(biāo)識資源的引用的,它的內(nèi)部包含了指向?qū)ο蟮奈募Y源描述符。

          一般情況下,當(dāng)我們想要將多個進(jìn)程監(jiān)聽到一個端口下,可能會考慮使用主進(jìn)程代理的方式處理:

          然而,這種代理方案會導(dǎo)致每次請求的接收和代理轉(zhuǎn)發(fā)用掉兩個文件描述符,而系統(tǒng)的文件描述符是有限的,這種方式會影響系統(tǒng)的擴(kuò)展能力。

          所以,為什么要使用句柄?原因是在實際應(yīng)用場景下,建立 IPC 通信后可能會涉及到比較復(fù)雜的數(shù)據(jù)處理場景,句柄可以作為 send() 方法的第二個可選參數(shù)傳入,也就是說可以直接將資源的標(biāo)識通過 IPC 傳輸,避免了上面所說的代理轉(zhuǎn)發(fā)造成的文件描述符的使用。


          以下是支持發(fā)送的句柄類型:

          • net.Socket
          • net.Server
          • net.Native
          • dgram.Socket
          • dgram.Native

          句柄發(fā)送與還原

          NodeJS 進(jìn)程之間只有消息傳遞,不會真正的傳遞對象。

          send() 方法在發(fā)送消息前,會將消息組裝成 handle 和 message,這個 message 會經(jīng)過 JSON.stringify 序列化,也就是說,傳遞句柄的時候,不會將整個對象傳遞過去,在 IPC 通道傳輸?shù)亩际亲址瑐鬏敽笸ㄟ^ JSON.parse 還原成對象。

          監(jiān)聽共同端口

          上圖所示,為什么多個進(jìn)程可以監(jiān)聽同一個端口呢?

          原因是主進(jìn)程通過 send() 方法向多個子進(jìn)程發(fā)送屬于該主進(jìn)程的一個服務(wù)對象的句柄,所以對于每一個子進(jìn)程而言,它們在還原句柄之后,得到的服務(wù)對象是一樣的,當(dāng)網(wǎng)絡(luò)請求向服務(wù)端發(fā)起時,進(jìn)程服務(wù)是搶占式的,所以監(jiān)聽相同端口時不會引起異常。

          Cluster

          引用 Egg.js 官方對 Cluster 的理解:

          • 在服務(wù)器上同時啟動多個進(jìn)程。
          • 每個進(jìn)程里都跑的是同一份源代碼(好比把以前一個進(jìn)程的工作分給多個進(jìn)程去做)。
          • 更神奇的是,這些進(jìn)程可以同時監(jiān)聽一個端口

          其中:

          • 負(fù)責(zé)啟動其他進(jìn)程的叫做 Master 進(jìn)程,他好比是個『包工頭』,不做具體的工作,只負(fù)責(zé)啟動其他進(jìn)程。
          • 其他被啟動的叫 Worker 進(jìn)程,顧名思義就是干活的『工人』。它們接收請求,對外提供服務(wù)。
          • Worker 進(jìn)程的數(shù)量一般根據(jù)服務(wù)器的 CPU 核數(shù)來定,這樣就可以完美利用多核資源。
          const cluster = require('cluster');
          const http = require('http');
          const numCPUs = require('os').cpus().length;

          if (cluster.isMaster) {
            // Fork workers.
            for (let i = 0; i < numCPUs; i++) {
              cluster.fork();
            }

            cluster.on('exit'function(worker, code, signal{
              console.log('worker ' + worker.process.pid + ' died');
            });
          else {
            // Workers can share any TCP connection
            // In this case it is an HTTP server
            http.createServer(function(req, res{
              res.writeHead(200);
              res.end("hello world\n");
            }).listen(8000);
          }

          簡單來說,cluster 模塊是 child_process 模塊和 net 模塊的組合應(yīng)用。

          cluster 啟動時,內(nèi)部會啟動 TCP 服務(wù)器,將這個 TCP 服務(wù)器端 socket 的文件描述符發(fā)給工作進(jìn)程。

          cluster 模塊應(yīng)用中,一個主進(jìn)程只能管理一組工作進(jìn)程,其運作模式?jīng)]有 child_process 模塊那么靈活,但是更加穩(wěn)定:


          為了讓集群更加穩(wěn)定和健壯,cluster 模塊也暴露了許多事件:

          • fork
          • online
          • listening
          • disconnect
          • exit
          • setup

          這些事件在進(jìn)程間消息傳遞的基礎(chǔ)了完成了封裝,保證了集群的穩(wěn)定性和健壯性。

          進(jìn)程守護(hù)#

          未捕獲異常

          當(dāng)代碼拋出了異常沒有被捕獲到時,進(jìn)程將會退出,此時 Node.js 提供了 process.on('uncaughtException', handler) 接口來捕獲它,但是當(dāng)一個 Worker 進(jìn)程遇到未捕獲的異常時,它已經(jīng)處于一個不確定狀態(tài),此時我們應(yīng)該讓這個進(jìn)程優(yōu)雅退出:

          • 關(guān)閉異常 Worker 進(jìn)程所有的 TCP Server(將已有的連接快速斷開,且不再接收新的連接),斷開和 Master 的 IPC 通道,不再接受新的用戶請求。
          • Master 立刻 fork 一個新的 Worker 進(jìn)程,保證在線的『工人』總數(shù)不變。
          • 異常 Worker 等待一段時間,處理完已經(jīng)接受的請求后退出。
          +---------+                 +---------+
          |  Worker |                 |  Master |
          +---------+                 +----+----+
               | uncaughtException         |
               +------------+              |
               |            |              |                   +---------+
               | <----------+              |                   |  Worker |
               |                           |                   +----+----+
               |        disconnect         |   fork a new worker    |
               +-------------------------> + ---------------------> |
               |         wait...           |                        |
               |          exit             |                        |
               +-------------------------> |                        |
               |                           |                        |
              die                          |                        |
                                           |                        |
                                           |                        |

          OOM、系統(tǒng)異常

          當(dāng)一個進(jìn)程出現(xiàn)異常導(dǎo)致 crash 或者 OOM 被系統(tǒng)殺死時,不像未捕獲異常發(fā)生時我們還有機(jī)會讓進(jìn)程繼續(xù)執(zhí)行,只能夠讓當(dāng)前進(jìn)程直接退出,Master 立刻 fork 一個新的 Worker。

          參考資料

          • 《深入淺出 Node.js》
          • Node.js 中文文檔[1]
          • Egg.js 官方文檔[2]

          參考資料

          [1]

          Node.js 中文文檔: http://nodejs.cn/api/

          [2]

          Egg.js 官方文檔: https://eggjs.org/zh-cn/core/cluster-and-ipc.html



          內(nèi)推社群


          我組建了一個氛圍特別好的騰訊內(nèi)推社群,如果你對加入騰訊感興趣的話(后續(xù)有計劃也可以),我們可以一起進(jìn)行面試相關(guān)的答疑、聊聊面試的故事、并且在你準(zhǔn)備好的時候隨時幫你內(nèi)推。下方加 winty 好友回復(fù)「面試」即可。


          瀏覽 49
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产成人一区二区三区A片免费 | 国产99页 | 黄色片亚洲 | 国产成人做爱视频 | 大香蕉日韩视频 |