<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 多進程和集群

          共 8872字,需瀏覽 18分鐘

           ·

          2021-06-11 04:00

          進程

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

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

          多進程

          復制進程

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

          舉個??

          在一個目錄下新建 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 查看進程的數(shù)量,我們可以發(fā)現(xiàn),理想狀況下,進程的數(shù)量等于 CPU 的核心數(shù),每個進程各自利用一個 CPU,實現(xiàn)多核 CPU 的利用:


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

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

          子進程的創(chuàng)建

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

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

          對比:


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

          進程間的通信

          在 NodeJS 中,子進程對象使用 send() 方法實現(xiàn)主進程向子進程發(fā)送數(shù)據(jù),message 事件實現(xià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('主進程收到子進程的信息:', msg);
          });

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

          child.js

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

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

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


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

          IPC

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

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


          句柄傳遞

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

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

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

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


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

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

          句柄發(fā)送與還原

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

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

          監(jiān)聽共同端口

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

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

          Cluster

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

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

          其中:

          • 負責啟動其他進程的叫做 Master 進程,他好比是個『包工頭』,不做具體的工作,只負責啟動其他進程。
          • 其他被啟動的叫 Worker 進程,顧名思義就是干活的『工人』。它們接收請求,對外提供服務。
          • Worker 進程的數(shù)量一般根據(jù)服務器的 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 模塊的組合應用。

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

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


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

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

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

          進程守護#

          未捕獲異常

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

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

          OOM、系統(tǒng)異常

          當一個進程出現(xiàn)異常導致 crash 或者 OOM 被系統(tǒng)殺死時,不像未捕獲異常發(fā)生時我們還有機會讓進程繼續(xù)執(zhí)行,只能夠讓當前進程直接退出,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)容對你挺有啟發(fā),我想邀請你幫我三個小忙:

          1. 點個「在看」,讓更多的人也能看到這篇內(nèi)容(喜歡不點在看,都是耍流氓 -_-)
          2. 歡迎加我微信「TH0000666」一起交流學習...
          3. 關注公眾號「前端Sharing」,持續(xù)為你推送精選好文。




          瀏覽 65
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  国产特级黄色 | 羞涩无遮挡 | 精品视频免费在线观看 | 精品少妇久久久 | 米奇视频777 |