Node.js創(chuàng)建多進(jìn)程實(shí)踐

Node.js如何創(chuàng)建多進(jìn)程

這里我們?cè)谥鬟M(jìn)程cluster.isMaster中根據(jù)系統(tǒng)CPU的總核數(shù)require('os').cpus()創(chuàng)建多個(gè)工作進(jìn)程cluster.fork()。在各工作進(jìn)程即子進(jìn)程中創(chuàng)建HTTP服務(wù)器,監(jiān)聽(tīng)同一端口號(hào)8090并返回響應(yīng)。具體實(shí)現(xiàn)如下圖所示:

image.png

通過(guò)上面的處理邏輯,我發(fā)現(xiàn)在主進(jìn)程中只是執(zhí)行了創(chuàng)建子進(jìn)程的動(dòng)作,并沒(méi)有創(chuàng)建服務(wù)器的動(dòng)作。那么主進(jìn)程的服務(wù)器是如何創(chuàng)建的呢?由于服務(wù)器創(chuàng)建的動(dòng)作是在子進(jìn)程中執(zhí)行的,因此主進(jìn)程是否就離不開(kāi)子進(jìn)程的交互了。

Q:主進(jìn)程在cluster模式下如何創(chuàng)建服務(wù)器?

關(guān)于集群,你應(yīng)該知道的事兒

在集群模式下,主進(jìn)程的服務(wù)器會(huì)接受到請(qǐng)求然后發(fā)送給子進(jìn)程。而主進(jìn)程服務(wù)器的創(chuàng)建當(dāng)然和子進(jìn)程密切相關(guān)了。下面詳細(xì)分析一下:


image.png

子進(jìn)程在cluster._getServer函數(shù)中向已建立的IPC通道發(fā)送內(nèi)部消息message,該消息包含serverQuery信息,同時(shí)包含act: 'queryServer'字段,等待服務(wù)器響應(yīng)后繼續(xù)執(zhí)行回調(diào)函數(shù)modifyHandle。

主進(jìn)程internal/cluster/master.js中會(huì)監(jiān)聽(tīng)message。

function onmessage(message, handle) {
  const worker = this;

  if (message.act === 'online')
    online(worker);
  else if (message.act === 'queryServer')
    queryServer(worker, message);
  else if (message.act === 'listening')
    listening(worker, message);
  else if (message.act === 'exitedAfterDisconnect')
    exitedAfterDisconnect(worker, message);
  else if (message.act === 'close')
    close(worker, message);
}

主進(jìn)程接收到子進(jìn)程發(fā)送到內(nèi)部消息,會(huì)根據(jù)act:'queryServer'執(zhí)行對(duì)應(yīng)queryServer()方法,完成服務(wù)器到創(chuàng)建,同時(shí)發(fā)送回復(fù)消息給子進(jìn)程,子進(jìn)程執(zhí)行回調(diào)函數(shù)modifyHandle,繼續(xù)接下來(lái)到操作。

Q:為什么可以通過(guò)cluster.isMaster判斷是主進(jìn)程還是子進(jìn)程呢?

這里就需要查看Node.js的具體實(shí)現(xiàn)了。我們可以發(fā)現(xiàn)在Node.js的cluster模塊中只有一行處理代碼,如下所示:

const childOrMaster = 'NODE_UNIQUE_ID' in process.env ? 'child' : 'master';
module.exports = require(`internal/cluster/${childOrMaster}`);

其中NODE_UNIQUE_ID變量默認(rèn)是沒(méi)有的,所以默認(rèn)創(chuàng)建的是主進(jìn)程。而變量NODE_UNIQUE_ID是在主進(jìn)程fork子進(jìn)程時(shí)傳遞進(jìn)去的參數(shù),因此采用cluster.fork()創(chuàng)建的子進(jìn)程是一定包含NODE_UNIQUE_ID的,具體流程如下圖所示:

cluster.isMaster區(qū)分主進(jìn)程和子進(jìn)程.png

??這里需要指出的是,必須通過(guò)cluster.fork創(chuàng)建的子進(jìn)程才有NODE_UNIQUE_ID變量,如果通過(guò)child_process.fork的子進(jìn)程,在不傳遞環(huán)境變量的情況下是沒(méi)有NODE_UNIQUE_ID的。因此,當(dāng)你在child_process.fork的子進(jìn)程中執(zhí)行cluster.isMaster判斷時(shí),返回 true。

Q:如何做到多個(gè)子進(jìn)程共同監(jiān)聽(tīng)一個(gè)端口號(hào)的?

我們都知道,同一個(gè)端口號(hào)是不能同時(shí)被多個(gè)進(jìn)程監(jiān)聽(tīng)的,如果有兩個(gè)進(jìn)程同時(shí)對(duì)一個(gè)端口進(jìn)行監(jiān)聽(tīng),Node.js會(huì)直接拋出一個(gè)異常(Error: listen EADDRINUSE)。

但是如果使用代理模式同時(shí)監(jiān)聽(tīng)多個(gè)端口,讓master進(jìn)程監(jiān)聽(tīng)8090端口,收到請(qǐng)求時(shí),再將請(qǐng)求分發(fā)給不同服務(wù),而且master進(jìn)程還能做適當(dāng)?shù)呢?fù)載均衡。

首先我們先啟動(dòng)項(xiàng)目,查看系統(tǒng)端口占用情況,以便后期分析:

  1. 啟動(dòng)項(xiàng)目,但是不發(fā)起任何請(qǐng)求,此時(shí)應(yīng)該只有主進(jìn)程在運(yùn)行。


    image.png
  2. 發(fā)起請(qǐng)求,主進(jìn)程開(kāi)始分配任務(wù)給工作進(jìn)程執(zhí)行。


    image.png

    通過(guò)上圖,可以發(fā)現(xiàn)主進(jìn)程監(jiān)聽(tīng)8090端口,并且對(duì)請(qǐng)求進(jìn)行分配轉(zhuǎn)發(fā)到各工作進(jìn)程。這就是Master-Worker模式,又稱(chēng)主從模式。是典型的分布式架構(gòu)中用于并行處理業(yè)務(wù)的模式,具備較好的可伸縮性(很好的處理并發(fā)情況)和穩(wěn)定性(一個(gè)進(jìn)程掛掉不會(huì)影響其它進(jìn)程)。

主進(jìn)程不負(fù)責(zé)具體的業(yè)務(wù)處理,而是負(fù)責(zé)調(diào)度和管理工作進(jìn)程,它是趨向于穩(wěn)定的。而工作進(jìn)程負(fù)責(zé)具體的業(yè)務(wù)處理。

Q:主進(jìn)程對(duì)請(qǐng)求進(jìn)行分配,是否做了負(fù)載均衡

對(duì)于這個(gè)問(wèn)題,我們?cè)诜?wù)上線后通過(guò)日志進(jìn)行打印分析,統(tǒng)計(jì)各工作進(jìn)程被調(diào)用次數(shù),分析該模塊是否已實(shí)現(xiàn)負(fù)載均衡。

  1. 方案一:在app.js中創(chuàng)建全局變量global.works = [ ];在每次請(qǐng)求的時(shí)候?qū)?dāng)前使用的工作進(jìn)程id添加到全局?jǐn)?shù)組中,并進(jìn)行統(tǒng)計(jì)分析。
    image.png

    該方案存在問(wèn)題:由于每個(gè)子進(jìn)程是單獨(dú)創(chuàng)建到服務(wù)實(shí)例 http.createServer(app); 。。。全局變量global.works每次會(huì)被重置,因此沒(méi)有只能看當(dāng)當(dāng)次請(qǐng)求所使用當(dāng)進(jìn)程情況。

通過(guò)fork()復(fù)制的進(jìn)程都是一個(gè)獨(dú)立的進(jìn)程,每個(gè)進(jìn)程中有著獨(dú)立而全新的V8實(shí)例。

  1. 方案二:在主進(jìn)程中創(chuàng)建全局變量,并監(jiān)聽(tīng)包含notifyRequest的消息對(duì)子進(jìn)程的調(diào)用進(jìn)行統(tǒng)計(jì)分析。
    app.js process.send({cmd:'notifyRequest'});//記錄子進(jìn)程調(diào)用次數(shù)使用 返回notifyRequest消息
image.png

監(jiān)控結(jié)果展示:


image.png

根據(jù)監(jiān)控結(jié)果展示,發(fā)現(xiàn)Node.js的集群模式已經(jīng)實(shí)現(xiàn)了負(fù)載均衡。

參考:

http://nodejs.cn/api/cluster.html#cluster_event_message

http://nodejs.cn/api/child_process.html

Q1:為什么方案二能統(tǒng)計(jì)到所有進(jìn)程調(diào)度到情況?

  1. 在app.js中使用
app.use((req, res, next)=>{
  process.send({cmd:'notifyRequest'});//記錄子進(jìn)程調(diào)用次數(shù)使用
  console.log(`工作進(jìn)程${cluster.worker.id} 正在端口${cluster.worker.process.pid}運(yùn)行`);
  next();
})

是為了在每次請(qǐng)求(app.use()匹配了所有/路由)的時(shí)候發(fā)送特定信息給各進(jìn)程。

  1. 在主進(jìn)程中創(chuàng)建全局變量global.workers = [];//子進(jìn)程調(diào)用次數(shù)統(tǒng)計(jì)數(shù)組
  2. 各個(gè)工作進(jìn)程中監(jiān)聽(tīng)message消息,只要有進(jìn)程接受到請(qǐng)求信息就將當(dāng)前進(jìn)程的idpush到全局變量中,并對(duì)全局變量中的信息進(jìn)行統(tǒng)計(jì)分析。

如果Node.js進(jìn)程是通過(guò)進(jìn)程間通信產(chǎn)生的,那么,process.send()方法可以用來(lái)給父進(jìn)程發(fā)送消息。 接收到的消息被視為父進(jìn)程的ChildProcess對(duì)象上的一個(gè)'message'事件。

如果Node.js進(jìn)程不是通過(guò)進(jìn)程間通信產(chǎn)生的, process.send() 會(huì)是undefined。

所以說(shuō)主進(jìn)程和各工作進(jìn)程之間是通過(guò)消息傳遞內(nèi)容,而不是共享或直接操作相關(guān)資源。??通過(guò)fork()或者其它API創(chuàng)建子進(jìn)程后,為實(shí)現(xiàn)父子進(jìn)程之間的通信,父進(jìn)程和子進(jìn)程之間會(huì)創(chuàng)建IPC通道(通過(guò)IPC通道,父子進(jìn)程之間才能通過(guò)message和send()傳遞消息)。

Q:負(fù)載均衡是如何實(shí)現(xiàn)的?

Node.js在實(shí)現(xiàn)負(fù)載均衡上有至少兩種處理方式:

  1. 搶占式策略
  2. Round-Robin 輪叫調(diào)度

由于單個(gè)Node程序僅僅利用單核CPU,因此為了更好利用系統(tǒng)資源就需要fork多個(gè)Node進(jìn)程來(lái)執(zhí)行HTTP服務(wù)器邏輯,所以Node內(nèi)建模塊提供了child_processcluster模塊。

Q: child_processcluster模塊的區(qū)別

  • 利用child_process模塊,我們可以執(zhí)行shell命令,可以fork子進(jìn)程執(zhí)行代碼,也可以直接執(zhí)行二進(jìn)制文件;
  • 利用cluster模塊,使用node封裝好的API、IPC通道和調(diào)度機(jī)可以非常簡(jiǎn)單的創(chuàng)建包括一個(gè)master進(jìn)程下HTTP代理服務(wù)器 + 多個(gè)worker進(jìn)程多個(gè)HTTP應(yīng)用服務(wù)器的架構(gòu),并提供兩種調(diào)度子進(jìn)程算法。

Q:多進(jìn)程之間的共享Session實(shí)現(xiàn)。

背景描述:在項(xiàng)目接入cluster模塊實(shí)現(xiàn)多進(jìn)程處理后,發(fā)現(xiàn)項(xiàng)目啟動(dòng)后,會(huì)出現(xiàn)請(qǐng)求異常(Session丟失)導(dǎo)致頁(yè)面空白。


image.png

分析發(fā)現(xiàn)是因?yàn)樵谶M(jìn)入系統(tǒng)后,會(huì)有多個(gè)請(qǐng)求,各請(qǐng)求可能被轉(zhuǎn)發(fā)到不同到工作進(jìn)程(不同的進(jìn)程是不同的實(shí)例),因此會(huì)出現(xiàn)請(qǐng)求中攜帶的Session丟失,導(dǎo)致異常。查看解決方案發(fā)現(xiàn),Express模塊提供了express-session模塊,可保存session。

var express = require('express');
var cookieParser = require('cookie-parser');
var session = require('express-session');
var RedisStore = require('connect-redis')(session);
app.use(session({
    'secret': '12345',
    'name': 'fecarApp', //這里的name值得是cookie的name,默認(rèn)cookie的name是:connect.sid
    'cookie': { maxAge: 8000000 }, //設(shè)置maxAge是80000ms,即80s后session和相應(yīng)的cookie失效過(guò)期
    'resave': false,
    'saveUninitialized': true,
    'store': new RedisStore(options),
    genid: function (req) {
        // 如果沒(méi)有 ticket 就隨機(jī)生成
        if (!req.query.ticket) return uid(24)
        // 如果有 ticket 就把 ticket MD5加密返回
        return MD5(req.query.ticket)
    }
}));

參考:
https://www.cnblogs.com/chenchenluo/p/4197181.html

Node.jsos模塊獲取CPU信息

require('os').cpus();返回一個(gè)對(duì)象數(shù)組,如下圖所示,包含所安裝的每個(gè) CPU/內(nèi)核的信息。

require('os').cpus().png

require('os').cpus().length;返回是總核數(shù)(總核數(shù) = 物理CPU個(gè)數(shù) X 每顆物理CPU的核數(shù))。

拿我本機(jī)來(lái)說(shuō),查看系統(tǒng)配置發(fā)現(xiàn)核總數(shù)為2(物理CPU數(shù)目)。使用如上代碼查看發(fā)現(xiàn)是4(核總數(shù)),說(shuō)明是雙核CPU。

image.png
image.png
# 總核數(shù) = 物理CPU個(gè)數(shù) X 每顆物理CPU的核數(shù) 
# 總邏輯CPU數(shù) = 物理CPU個(gè)數(shù) X 每顆物理CPU的核數(shù) X 超線程數(shù)

# 查看物理CPU個(gè)數(shù)
cat /proc/cpuinfo| grep "physical id"| sort| uniq| wc -l

# 查看每個(gè)物理CPU中core的個(gè)數(shù)(即核數(shù))
cat /proc/cpuinfo| grep "cpu cores"| uniq

# 查看邏輯CPU的個(gè)數(shù)
cat /proc/cpuinfo| grep "processor"| wc -l

參考

https://www.cnblogs.com/zmxmumu/p/6179503.html

https://blog.csdn.net/feijiges/article/details/76860372

https://segmentfault.com/a/1190000016169207

https://www.cnblogs.com/emanlee/p/3587571.html

Node.js采取cluster模塊創(chuàng)建多進(jìn)程后無(wú)法開(kāi)啟調(diào)試模式

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容