<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>

          【Node.js】淺析 NodeJS 多進(jìn)程和集群

          共 8752字,需瀏覽 18分鐘

           ·

          2021-07-21 15:06


          進(jìn)程

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

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

          多進(jìn)程

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

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

          舉個(gè)??

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

          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');
          });

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


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

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

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

          child_process 模塊提供了四個(gè)方法創(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)

          對(duì)比:


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

          進(jìn)程間的通信

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

          舉個(gè)??

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

          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)程收到來(lái)自主進(jìn)程的信息:', msg);
          });

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

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


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

          IPC

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

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


          句柄傳遞

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

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

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

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


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

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

          句柄發(fā)送與還原

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

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

          監(jiān)聽(tīng)共同端口

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

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

          Cluster

          引用 Egg.js 官方對(duì) Cluster 的理解:

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

          其中:

          • 負(fù)責(zé)啟動(dòng)其他進(jìn)程的叫做 Master 進(jìn)程,他好比是個(gè)『包工頭』,不做具體的工作,只負(fù)責(zé)啟動(dòng)其他進(jìn)程。
          • 其他被啟動(dòng)的叫 Worker 進(jìn)程,顧名思義就是干活的『工人』。它們接收請(qǐng)求,對(duì)外提供服務(wù)。
          • Worker 進(jìn)程的數(shù)量一般根據(jù)服務(wù)器的 CPU 核數(shù)來(lái)定,這樣就可以完美利用多核資源。
          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);
          }

          簡(jiǎn)單來(lái)說(shuō),cluster 模塊是 child_process 模塊和 net 模塊的組合應(yīng)用。

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

          cluster 模塊應(yīng)用中,一個(gè)主進(jìn)程只能管理一組工作進(jìn)程,其運(yù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)代碼拋出了異常沒(méi)有被捕獲到時(shí),進(jìn)程將會(huì)退出,此時(shí) Node.js 提供了 process.on('uncaughtException', handler) 接口來(lái)捕獲它,但是當(dāng)一個(gè) Worker 進(jìn)程遇到未捕獲的異常時(shí),它已經(jīng)處于一個(gè)不確定狀態(tài),此時(shí)我們應(yīng)該讓這個(gè)進(jìn)程優(yōu)雅退出:

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

          OOM、系統(tǒng)異常

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


          瀏覽 68
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  国产在线8 | 亚洲高清无码在线观看视频 | 亚洲AV蜜桃永久无码精品色哟 | 波多野结衣av一区二区全免费观看 | 日韩欧美精品 |