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

溫馨提示×

溫馨提示×

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

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

詳解通過源碼解析Node.js中cluster模塊的主要功能實現

發布時間:2020-10-04 03:53:46 來源:腳本之家 閱讀:156 作者:DavidCai1993 欄目:web開發

眾所周知,Node.js中的JavaScript代碼執行在單線程中,非常脆弱,一旦出現了未捕獲的異常,那么整個應用就會崩潰。這在許多場景下,尤其是web應用中,是無法忍受的。通常的解決方案,便是使用Node.js中自帶的cluster模塊,以master-worker模式啟動多個應用實例。然而大家在享受cluster模塊帶來的福祉的同時,不少人也開始好奇:

  1. 為什么我的應用代碼中明明有app.listen(port);,但cluter模塊在多次fork這份代碼時,卻沒有報端口已被占用?
  2. Master是如何將接收的請求傳遞至worker中進行處理然后響應的?

讓我們從Node.js項目的lib/cluster.js中的代碼里,來一勘究竟。

問題一

為了得到這個問題的解答,我們先從worker進程的初始化看起,master進程在fork工作進程時,會為其附上環境變量NODE_UNIQUE_ID,是一個從零開始的遞增數:

// lib/cluster.js
// ...

function createWorkerProcess(id, env) {
 // ...
 workerEnv.NODE_UNIQUE_ID = '' + id;

 // ...
 return fork(cluster.settings.exec, cluster.settings.args, {
  env: workerEnv,
  silent: cluster.settings.silent,
  execArgv: execArgv,
  gid: cluster.settings.gid,
  uid: cluster.settings.uid
 });
}

隨后Node.js在初始化時,會根據該環境變量,來判斷該進程是否為cluster模塊fork出的工作進程,若是,則執行workerInit()函數來初始化環境,否則執行masterInit()函數。

在workerInit()函數中,定義了cluster._getServer方法,這個方法在任何net.Server實例的listen方法中,會被調用:

// lib/net.js
// ...

function listen(self, address, port, addressType, backlog, fd, exclusive) {
 exclusive = !!exclusive;

 if (!cluster) cluster = require('cluster');

 if (cluster.isMaster || exclusive) {
  self._listen2(address, port, addressType, backlog, fd);
  return;
 }

 cluster._getServer(self, {
  address: address,
  port: port,
  addressType: addressType,
  fd: fd,
  flags: 0
 }, cb);

 function cb(err, handle) {
  // ...

  self._handle = handle;
  self._listen2(address, port, addressType, backlog, fd);
 }
}

你可能已經猜到,問題一的答案,就在這個cluster._getServer函數的代碼中。它主要干了兩件事:

  1. 向master進程注冊該worker,若master進程是第一次接收到監聽此端口/描述符下的worker,則起一個內部TCP服務器,來承擔監聽該端口/描述符的職責,隨后在master中記錄下該worker。
  2. Hack掉worker進程中的net.Server實例的listen方法里監聽端口/描述符的部分,使其不再承擔該職責。

對于第一件事,由于master在接收,傳遞請求給worker時,會符合一定的負載均衡規則(在非Windows平臺下默認為輪詢),這些邏輯被封裝在RoundRobinHandle類中。故,初始化內部TCP服務器等操作也在此處:

// lib/cluster.js
// ...

function RoundRobinHandle(key, address, port, addressType, backlog, fd) {
 // ...
 this.handles = [];
 this.handle = null;
 this.server = net.createServer(assert.fail);

 if (fd >= 0)
  this.server.listen({ fd: fd });
 else if (port >= 0)
  this.server.listen(port, address);
 else
  this.server.listen(address); // UNIX socket path.

 /// ...
}

對于第二件事,由于net.Server實例的listen方法,最終會調用自身_handle屬性下listen方法來完成監聽動作,故在代碼中修改之:

// lib/cluster.js
// ...

function rr(message, cb) {
 // ...
 // 此處的listen函數不再做任何監聽動作
 function listen(backlog) {
  return 0;
 }

 function close() {
  // ...
 }
 function ref() {}
 function unref() {}

 var handle = {
  close: close,
  listen: listen,
  ref: ref,
  unref: unref,
 };
 // ...
 handles[key] = handle;
 cb(0, handle); // 傳入這個cb中的handle將會被賦值給net.Server實例中的_handle屬性
}

// lib/net.js
// ...
function listen(self, address, port, addressType, backlog, fd, exclusive) {
 // ...

 if (cluster.isMaster || exclusive) {
  self._listen2(address, port, addressType, backlog, fd);
  return; // 僅在worker環境下改變
 }

 cluster._getServer(self, {
  address: address,
  port: port,
  addressType: addressType,
  fd: fd,
  flags: 0
 }, cb);

 function cb(err, handle) {
  // ...
  self._handle = handle;
  // ...
 }
}

至此,第一個問題便已豁然開朗了,總結下:

  1. 端口僅由master進程中的內部TCP服務器監聽了一次。
  2. 不會出現端口被重復監聽報錯,是由于,worker進程中,最后執行監聽端口操作的方法,已被cluster模塊主動hack。

問題二

解決了問題一,問題二的解決就明朗輕松許多了。通過問題一我們已得知,監聽端口的是master進程中創建的內部TCP服務器,所以第二個問題的解決,著手點就是該內部TCP服務器接手連接時,執行的操作。Cluster模塊的做法是,監聽該內部TCP服務器的connection事件,在監聽器函數里,有負載均衡地挑選出一個worker,向其發送newconn內部消息(消息體對象中包含cmd: 'NODE_CLUSTER'屬性)以及一個客戶端句柄(即connection事件處理函數的第二個參數),相關代碼如下:

// lib/cluster.js
// ...

function RoundRobinHandle(key, address, port, addressType, backlog, fd) {
 // ...
 this.server = net.createServer(assert.fail);
 // ...

 var self = this;
 this.server.once('listening', function() {
  // ...
  self.handle.onconnection = self.distribute.bind(self);
 });
}

RoundRobinHandle.prototype.distribute = function(err, handle) {
 this.handles.push(handle);
 var worker = this.free.shift();
 if (worker) this.handoff(worker);
};

RoundRobinHandle.prototype.handoff = function(worker) {
 // ...
 var message = { act: 'newconn', key: this.key };
 var self = this;
 sendHelper(worker.process, message, handle, function(reply) {
  // ...
 });
};

Worker進程在接收到了newconn內部消息后,根據傳遞過來的句柄,調用實際的業務邏輯處理并返回:

// lib/cluster.js
// ...

// 該方法會在Node.js初始化時由 src/node.js 調用
cluster._setupWorker = function() {
 // ...
 process.on('internalMessage', internal(worker, onmessage));

 // ...
 function onmessage(message, handle) {
  if (message.act === 'newconn')
   onconnection(message, handle);
  // ...
 }
};

function onconnection(message, handle) {
 // ...
 var accepted = server !== undefined;
 // ...
 if (accepted) server.onconnection(0, handle);
}

至此,問題二也得到了解決,也總結一下:

  1. 所有請求先同一經過內部TCP服務器。
  2. 在內部TCP服務器的請求處理邏輯中,有負載均衡地挑選出一個worker進程,將其發送一個newconn內部消息,隨消息發送客戶端句柄。
  3. Worker進程接收到此內部消息,根據客戶端句柄創建net.Socket實例,執行具體業務邏輯,返回。

最后

Node.js中的cluster模塊除了上述提到的功能外,其實還提供了非常豐富的API供master和worker進程之前通信,對于不同的操作系統平臺,也提供了不同的默認行為。本文僅挑選了一條功能線進行了分析闡述。如果大家有閑,非常推薦完整領略一下cluster模塊的代碼實現。

參考:

https://github.com/nodejs/node/blob/master/lib/cluster.js
https://github.com/nodejs/node/blob/master/lib/net.js

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。

向AI問一下細節

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

AI

铁岭市| 宣威市| 荣昌县| 华安县| 南康市| 南城县| 额尔古纳市| 辽中县| 庐江县| 蛟河市| 民丰县| 南城县| 滨海县| 乳山市| 海淀区| 璧山县| 通渭县| 嘉善县| 铜陵市| 长治市| 三门县| 宁陵县| 红安县| 永胜县| 阳春市| 内江市| 天峻县| 阿拉尔市| 鸡西市| 蛟河市| 廉江市| 泰和县| 日照市| 凤台县| 泾源县| 贺州市| 东乌珠穆沁旗| 六枝特区| 沂南县| 清涧县| 柘城县|