欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Node.js中的cluster模塊深入解讀

 更新時(shí)間:2018年06月11日 08:36:28   作者:Randal  
NodeJS引入了Cluster模塊試圖簡(jiǎn)化這些體力勞動(dòng),使用Cluster模塊可以運(yùn)行并管理多個(gè)實(shí)例進(jìn)程,下面這篇文章主要給大家介紹了關(guān)于Node.js中cluster模塊的相關(guān)資料,需要的朋友可以參考借鑒,下面來一起看看吧

預(yù)備知識(shí)

在如今機(jī)器的CPU都是多核的背景下,Node的單線程設(shè)計(jì)已經(jīng)沒法更充分的"壓榨"機(jī)器性能了。所以從v0.8開始,Node新增了一個(gè)內(nèi)置模塊——“cluster”,故名思議,它可以通過一個(gè)父進(jìn)程管理一坨子進(jìn)程的方式來實(shí)現(xiàn)集群的功能。

學(xué)習(xí)cluster之前,需要了解process相關(guān)的知識(shí),如果不了解的話建議先閱讀process模塊、child_process模塊。

cluster借助child_process模塊的fork()方法來創(chuàng)建子進(jìn)程,通過fork方式創(chuàng)建的子進(jìn)程與父進(jìn)程之間建立了IPC通道,支持雙向通信。

cluster模塊最早出現(xiàn)在node.js v0.8版本中

為什么會(huì)存在cluster模塊?

Node.js是單線程的,那么如果希望利用服務(wù)器的多核的資源的話,就應(yīng)該多創(chuàng)建幾個(gè)進(jìn)程,由多個(gè)進(jìn)程共同提供服務(wù)。如果直接采用下列方式啟動(dòng)多個(gè)服務(wù)的話,會(huì)提示端口占用。

const http = require('http');
http.createServer((req, res) => {
 res.writeHead(200);
 res.end('hello world\n');
}).listen(8000);

// 啟動(dòng)第一個(gè)服務(wù) node index.js &
// 啟動(dòng)第二個(gè)服務(wù) node index.js &

 throw er; // Unhandled 'error' event
 ^

Error: listen EADDRINUSE :::8000
 at Server.setupListenHandle [as _listen2] (net.js:1330:14)
 at listenInCluster (net.js:1378:12)
 at Server.listen (net.js:1465:7)
 at Object.<anonymous> (/Users/xiji/workspace/learn/node-basic/cluster/simple.js:5:4)
 at Module._compile (internal/modules/cjs/loader.js:702:30)
 at Object.Module._extensions..js (internal/modules/cjs/loader.js:713:10)
 at Module.load (internal/modules/cjs/loader.js:612:32)
 at tryModuleLoad (internal/modules/cjs/loader.js:551:12)
 at Function.Module._load (internal/modules/cjs/loader.js:543:3)
 at Function.Module.runMain (internal/modules/cjs/loader.js:744:10)

如果改用cluster的話就沒有問題

const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;

if (cluster.isMaster) {
 console.log(`Master ${process.pid} is running`);

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

 cluster.on('exit', (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((req, res) => {
 res.writeHead(200);
 res.end('hello world\n');
 }).listen(8000);

 console.log(`Worker ${process.pid} started`);
}

// node index.js 執(zhí)行完啟動(dòng)了一個(gè)主進(jìn)程和8個(gè)子進(jìn)程(子進(jìn)程數(shù)與cpu核數(shù)相一致)
Master 11851 is running
Worker 11852 started
Worker 11854 started
Worker 11853 started
Worker 11855 started
Worker 11857 started
Worker 11858 started
Worker 11856 started
Worker 11859 started

cluster是如何實(shí)現(xiàn)多進(jìn)程共享端口的?

cluster創(chuàng)建的進(jìn)程分兩種,父進(jìn)程和子進(jìn)程,父進(jìn)程只有一個(gè),子進(jìn)程有多個(gè)(一般根據(jù)cpu核數(shù)創(chuàng)建)

  • 父進(jìn)程負(fù)責(zé)監(jiān)聽端口接受請(qǐng)求,然后分發(fā)請(qǐng)求。
  • 子進(jìn)程負(fù)責(zé)請(qǐng)求的處理。

有三個(gè)問題需要回答:

  • 子進(jìn)程為何調(diào)用listen不會(huì)進(jìn)行端口綁定
  • 父進(jìn)程何時(shí)創(chuàng)建的TCP Server
  • 父進(jìn)程是如何完成分發(fā)的

子進(jìn)程為何調(diào)用listen不會(huì)綁定端口?

net.js源碼中的listen方法通過listenInCluster方法來區(qū)分是父進(jìn)程還是子進(jìn)程,不同進(jìn)程的差異在listenInCluster方法中體現(xiàn)

function listenInCluster(server, address, port, addressType, backlog, fd, excluseive) {
 
 if (cluster.isMaster || exclusive) {
 server._listen2(address, port, addressType, backlog, fd);
 return;
 }

 const serverQuery = { address: address ......};

 cluster._getServer(server, serverQuery, listenOnMasterHandle);

 function listenOnMasterHandle(err, handle) {
 server._handle = handle;
 server._listen2(address, port, addressType, backlog, fd);
 }
}

上面是精簡(jiǎn)過的代碼,當(dāng)子進(jìn)程調(diào)用listen方法時(shí),會(huì)先執(zhí)行_getServer,然后通過callback的形式指定server._handle的值,之后再調(diào)用_listen2方法。

cluster._getServer = function(obj, options, cb) {
 ...
 const message = util._extend({
 act: 'queryServer',
 index: indexes[indexesKey],
 data: null
 }, options);

 message.address = address;

 send(message, (reply, handle) => {
 if (handle)
 shared(reply, handle, indexesKey, cb); // Shared listen socket.
 else
 rr(reply, indexesKey, cb); // Round-robin.
 });
 ...
};

_getServer方法會(huì)向主進(jìn)程發(fā)送queryServer的message,父進(jìn)程執(zhí)行完會(huì)調(diào)用回調(diào)函數(shù),根據(jù)是否返回handle來區(qū)分是調(diào)用shared方法還是rr方法,這里其實(shí)是會(huì)調(diào)用rr方法。而rr方法的主要作用就是偽造了TCPWrapper來調(diào)用net的listenOnMasterHandle回調(diào)函數(shù)

function rr(message, indexesKey, cb) {

 var key = message.key;

 function listen(backlog) {
 return 0;
 }

 function close() {
 if (key === undefined)
 return;

 send({ act: 'close', key });
 delete handles[key];
 delete indexes[indexesKey];
 key = undefined;
 }

 function getsockname(out) {
 if (key)
 util._extend(out, message.sockname);

 return 0;
 }

 const handle = { close, listen, ref: noop, unref: noop };
 handles[key] = handle;
 cb(0, handle);
}

由于子進(jìn)程的server拿到的是圍繞的TCPWrapper,當(dāng)調(diào)用listen方法時(shí)并不會(huì)執(zhí)行任何操作,所以在子進(jìn)程中調(diào)用listen方法并不會(huì)綁定端口,因而也并不會(huì)報(bào)錯(cuò)。

父進(jìn)程何時(shí)創(chuàng)建的TCP Server

在子進(jìn)程發(fā)送給父進(jìn)程的queryServer message時(shí),父進(jìn)程會(huì)檢測(cè)是否創(chuàng)建了TCP Server,如果沒有的話就會(huì)創(chuàng)建TCP Server并綁定端口,然后再把子進(jìn)程記錄下來,方便后續(xù)的用戶請(qǐng)求worker分發(fā)。

父進(jìn)程是如何完成分發(fā)的

父進(jìn)程由于綁定了端口號(hào),所以可以捕獲連接請(qǐng)求,父進(jìn)程的onconnection方法會(huì)被觸發(fā),onconnection方法觸發(fā)時(shí)會(huì)傳遞TCP對(duì)象參數(shù),由于之前父進(jìn)程記錄了所有的worker,所以父進(jìn)程可以選擇要處理請(qǐng)求的worker,然后通過向worker發(fā)送act為newconn的消息,并傳遞TCP對(duì)象,子進(jìn)程監(jiān)聽到消息后,對(duì)傳遞過來的TCP對(duì)象進(jìn)行封裝,封裝成socket,然后觸發(fā)connection事件。這樣就實(shí)現(xiàn)了子進(jìn)程雖然不監(jiān)聽端口,但是依然可以處理用戶請(qǐng)求的目的。

cluster如何實(shí)現(xiàn)負(fù)載均衡

負(fù)載均衡直接依賴cluster的請(qǐng)求調(diào)度策略,在v6.0版本之前,cluster的調(diào)用策略采用的是cluster.SCHED_NONE(依賴于操作系統(tǒng)),SCHED_NODE理論上來說性能最好(Ferando Micalli寫過一篇Node.js 6.0版本的cluster和iptables以及nginx性能對(duì)比的文章)但是從實(shí)際角度發(fā)現(xiàn),在請(qǐng)求調(diào)度方面會(huì)出現(xiàn)不太均勻的情況(可能出現(xiàn)8個(gè)子進(jìn)程中的其中2到3個(gè)處理了70%的連接請(qǐng)求)。因此在6.0版本中Node.js增加了cluster.SCHED_RR(round-robin),目前已成為默認(rèn)的調(diào)度策略(除了windows環(huán)境)

可以通過設(shè)置NODE_CLUSTER_SCHED_POLICY環(huán)境變量來修改調(diào)度策略

NODE_CLUSTER_SCHED_POLICY='rr'
NODE_CLUSTER_SCHED_POLICY='none'

或者設(shè)置cluster的schedulingPolicy屬性

cluster.schedulingPolicy = cluster.SCHED_NONE;
cluster.schedulingPolicy = cluster.SCHED_RR;

Node.js實(shí)現(xiàn)round-robin

Node.js內(nèi)部維護(hù)了兩個(gè)隊(duì)列:

  • free隊(duì)列記錄當(dāng)前可用的worker
  • handles隊(duì)列記錄需要處理的TCP請(qǐng)求

當(dāng)新請(qǐng)求到達(dá)的時(shí)候父進(jìn)程將請(qǐng)求暫存handles隊(duì)列,從free隊(duì)列中出隊(duì)一個(gè)worker,進(jìn)入worker處理(handoff)階段,關(guān)鍵邏輯實(shí)現(xiàn)如下:

RoundRobinHandle.prototype.distribute = function(err, handle) {
 this.handles.push(handle);
 const worker = this.free.shift();

 if (worker) {
 this.handoff(worker);
 }
};

worker處理階段首先從handles隊(duì)列出隊(duì)一個(gè)請(qǐng)求,然后通過進(jìn)程通信的方式通知子worker進(jìn)行請(qǐng)求處理,當(dāng)worker接收到通信消息后發(fā)送ack信息,繼續(xù)響應(yīng)handles隊(duì)列中的請(qǐng)求任務(wù),當(dāng)worker無法接受請(qǐng)求時(shí),父進(jìn)程負(fù)責(zé)重新調(diào)度worker進(jìn)行處理。關(guān)鍵邏輯如下:

RoundRobinHandle.prototype.handoff = function(worker) {
 const handle = this.handles.shift();
 if (handle === undefined) {
 this.free.push(worker); // Add to ready queue again.
 return;
 }

 const message = { act: 'newconn', key: this.key };
 sendHelper(worker.process, message, handle, (reply) => {
 if (reply.accepted)
 handle.close();
 else
 this.distribute(0, handle); // Worker is shutting down. Send to another.
 this.handoff(worker);
 });
};

注意:主進(jìn)程與子進(jìn)程之間建立了IPC,因此主進(jìn)程與子進(jìn)程之間可以通信,但是各個(gè)子進(jìn)程之間是相互獨(dú)立的(無法通信)

參考資料

https://medium.com/@fermads/node-js-process-load-balancing-comparing-cluster-iptables-and-nginx-6746aaf38272

總結(jié)

以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對(duì)腳本之家的支持。

相關(guān)文章

  • pm2與Verdaccio搭建私有npm庫(kù)過程詳解

    pm2與Verdaccio搭建私有npm庫(kù)過程詳解

    這篇文章主要介紹了pm2與Verdaccio搭建私有npm庫(kù)過程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-08-08
  • node.js根據(jù)不同請(qǐng)求路徑返回不同數(shù)據(jù)詳解流程

    node.js根據(jù)不同請(qǐng)求路徑返回不同數(shù)據(jù)詳解流程

    本篇文章介紹了我在開發(fā)過程中發(fā)現(xiàn)的一個(gè)小問題,就是node.js如何能夠根據(jù)不同的請(qǐng)求路徑來返回得到不同數(shù)據(jù),通讀本篇對(duì)大家的學(xué)習(xí)或工作具有一定的價(jià)值,需要的朋友可以參考下
    2021-10-10
  • node.js實(shí)現(xiàn)簡(jiǎn)單的壓縮/解壓縮功能示例

    node.js實(shí)現(xiàn)簡(jiǎn)單的壓縮/解壓縮功能示例

    這篇文章主要介紹了node.js實(shí)現(xiàn)簡(jiǎn)單的壓縮/解壓縮功能,結(jié)合實(shí)例形式分析了node.js實(shí)現(xiàn)本地文件與服務(wù)器端壓縮/解壓縮相關(guān)操作技巧,需要的朋友可以參考下
    2019-11-11
  • 淺談在koa2中實(shí)現(xiàn)頁(yè)面渲染的全局?jǐn)?shù)據(jù)

    淺談在koa2中實(shí)現(xiàn)頁(yè)面渲染的全局?jǐn)?shù)據(jù)

    本篇文章主要介紹了淺談在koa2中實(shí)現(xiàn)頁(yè)面渲染的全局?jǐn)?shù)據(jù),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-10-10
  • 深入理解Node.js中的Worker線程

    深入理解Node.js中的Worker線程

    這篇文章主要介紹了深入理解Node.js中的Worker線程,對(duì)Worker線程感興趣的同學(xué),一定要看一下
    2021-04-04
  • 云服務(wù)器部署Node.js項(xiàng)目的方法步驟(小白系列)

    云服務(wù)器部署Node.js項(xiàng)目的方法步驟(小白系列)

    這篇文章主要介紹了云服務(wù)器部署Node.js項(xiàng)目的方法步驟,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-03-03
  • Windows中安裝nvm進(jìn)行Node版本控制與詳細(xì)使用教程

    Windows中安裝nvm進(jìn)行Node版本控制與詳細(xì)使用教程

    nvm和npm都是node.js版本管理工具,但是為了解決node各種不同之間版本存在不兼容的問題,因此可以通過nvm安裝和切換不同版本的node,感興趣的可以了解一下
    2023-09-09
  • node.js事件輪詢機(jī)制原理知識(shí)點(diǎn)

    node.js事件輪詢機(jī)制原理知識(shí)點(diǎn)

    在本篇文章里小編給大家分享的是一篇關(guān)于node.js事件輪詢機(jī)制的相關(guān)知識(shí)點(diǎn)文章,有興趣的朋友們可以參考下。
    2019-12-12
  • 詳解axios在node.js中的post使用

    詳解axios在node.js中的post使用

    最近因?yàn)楣ぷ鞯脑蛟趯W(xué)習(xí)使用網(wǎng)絡(luò)請(qǐng)求庫(kù),因?yàn)檫@個(gè)項(xiàng)目用的是Promise,所以就選擇了axios,下面這篇文章主要給大家介紹了關(guān)于axios在node.js中的post使用的相關(guān)資料,文中介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來一起學(xué)習(xí)學(xué)習(xí)吧。
    2017-04-04
  • Node版本升級(jí)和降級(jí)之node版本管理工具nvm詳解

    Node版本升級(jí)和降級(jí)之node版本管理工具nvm詳解

    nvm是管理node版本的工具,一個(gè)電腦中可以安裝多個(gè)node版本,當(dāng)我們想使用哪個(gè)版本就切換成哪個(gè)版本,而nvm則是提供切換node版本的工具,這篇文章主要給大家介紹了關(guān)于Node版本升級(jí)和降級(jí)之node版本管理工具nvm的相關(guān)資料,需要的朋友可以參考下
    2022-08-08

最新評(píng)論