中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

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

發布時間:2020-09-02 15:10:54 來源:腳本之家 閱讀:210 作者:Randal 欄目:web開發

預備知識

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

學習cluster之前,需要了解process相關的知識,如果不了解的話建議先閱讀process模塊、child_process模塊。

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

cluster模塊最早出現在node.js v0.8版本中

為什么會存在cluster模塊?

Node.js是單線程的,那么如果希望利用服務器的多核的資源的話,就應該多創建幾個進程,由多個進程共同提供服務。如果直接采用下列方式啟動多個服務的話,會提示端口占用。

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

// 啟動第一個服務 node index.js &
// 啟動第二個服務 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 執行完啟動了一個主進程和8個子進程(子進程數與cpu核數相一致)
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是如何實現多進程共享端口的?

cluster創建的進程分兩種,父進程和子進程,父進程只有一個,子進程有多個(一般根據cpu核數創建)

  • 父進程負責監聽端口接受請求,然后分發請求。
  • 子進程負責請求的處理。

有三個問題需要回答:

  • 子進程為何調用listen不會進行端口綁定
  • 父進程何時創建的TCP Server
  • 父進程是如何完成分發的

子進程為何調用listen不會綁定端口?

net.js源碼中的listen方法通過listenInCluster方法來區分是父進程還是子進程,不同進程的差異在listenInCluster方法中體現

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

上面是精簡過的代碼,當子進程調用listen方法時,會先執行_getServer,然后通過callback的形式指定server._handle的值,之后再調用_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方法會向主進程發送queryServer的message,父進程執行完會調用回調函數,根據是否返回handle來區分是調用shared方法還是rr方法,這里其實是會調用rr方法。而rr方法的主要作用就是偽造了TCPWrapper來調用net的listenOnMasterHandle回調函數

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

由于子進程的server拿到的是圍繞的TCPWrapper,當調用listen方法時并不會執行任何操作,所以在子進程中調用listen方法并不會綁定端口,因而也并不會報錯。

父進程何時創建的TCP Server

在子進程發送給父進程的queryServer message時,父進程會檢測是否創建了TCP Server,如果沒有的話就會創建TCP Server并綁定端口,然后再把子進程記錄下來,方便后續的用戶請求worker分發。

父進程是如何完成分發的

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

cluster如何實現負載均衡

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

可以通過設置NODE_CLUSTER_SCHED_POLICY環境變量來修改調度策略

NODE_CLUSTER_SCHED_POLICY='rr'
NODE_CLUSTER_SCHED_POLICY='none'

或者設置cluster的schedulingPolicy屬性

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

Node.js實現round-robin

Node.js內部維護了兩個隊列:

  • free隊列記錄當前可用的worker
  • handles隊列記錄需要處理的TCP請求

當新請求到達的時候父進程將請求暫存handles隊列,從free隊列中出隊一個worker,進入worker處理(handoff)階段,關鍵邏輯實現如下:

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

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

worker處理階段首先從handles隊列出隊一個請求,然后通過進程通信的方式通知子worker進行請求處理,當worker接收到通信消息后發送ack信息,繼續響應handles隊列中的請求任務,當worker無法接受請求時,父進程負責重新調度worker進行處理。關鍵邏輯如下:

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

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

參考資料

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

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對億速云的支持。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

比如县| 磐安县| 涪陵区| 封丘县| 信宜市| 富民县| 乌苏市| 乌拉特中旗| 松潘县| 永州市| 河池市| 金山区| 南充市| 屏边| 定安县| 都兰县| 波密县| 西城区| 志丹县| 银川市| 静海县| 镇安县| 中宁县| 商都县| 泽州县| 吉水县| 大渡口区| 德江县| 瓮安县| 临颍县| 巍山| 昆明市| 滨州市| 河北省| 伽师县| 临桂县| 即墨市| 轮台县| 瑞丽市| 中牟县| 临沂市|