使用Node.js怎么實(shí)現(xiàn)進(jìn)程管理

這期內(nèi)容當(dāng)中小編將會(huì)給大家?guī)?lái)有關(guān)使用Node.js怎么實(shí)現(xiàn)進(jìn)程管理,文章內(nèi)容豐富且以專業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。

我們提供的服務(wù)有:成都網(wǎng)站建設(shè)、成都做網(wǎng)站、微信公眾號(hào)開(kāi)發(fā)、網(wǎng)站優(yōu)化、網(wǎng)站認(rèn)證、廣西ssl等。為上1000家企事業(yè)單位解決了網(wǎng)站和推廣的問(wèn)題。提供周到的售前咨詢和貼心的售后服務(wù),是有科學(xué)管理、有技術(shù)的廣西網(wǎng)站制作公司

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

node提供了child_process模塊用來(lái)進(jìn)行子進(jìn)程的創(chuàng)建,該模塊一共有四個(gè)方法用來(lái)創(chuàng)建子進(jìn)程。

const { spawn, exec, execFile, fork } = require('child_process')

spawn(command[, args][, options])

exec(command[, options][, callback])

execFile(file[, args][, options][, callback])

fork(modulePath[, args][, options])

spawn

首先認(rèn)識(shí)一下spawn方法,下面是Node文檔的官方實(shí)例。

const { spawn } = require('child_process');
const child = spawn('ls', ['-lh', '/home']);

child.on('close', (code) => {
 console.log(`子進(jìn)程退出碼:$[code]`);
});

const { stdin, stdout, stderr } = child

stdout.on('data', (data) => {
 console.log(`stdout: ${data}`);
});

stderr.on('data', (data) => {
 console.log(`stderr: ${data}`);
});

通過(guò)spawn創(chuàng)建的子進(jìn)程,繼承自EventEmitter,所以可以在上面進(jìn)行事件(discount,error,close,message)的監(jiān)聽(tīng)。同時(shí)子進(jìn)程具有三個(gè)輸入輸出流:stdin、stdout、stderr,通過(guò)這三個(gè)流,可以實(shí)時(shí)獲取子進(jìn)程的輸入輸出和錯(cuò)誤信息。

這個(gè)方法的最終實(shí)現(xiàn)基于libuv,這里不再展開(kāi)討論,感興趣可以查看源碼。

// 調(diào)用libuv的api,初始化一個(gè)進(jìn)程
int err = uv_spawn(env->event_loop(), &wrap->process_, &options);

exec/execFile

之所以把這兩個(gè)放到一起,是因?yàn)閑xec最后調(diào)用的就是execFile方法。唯一的區(qū)別是,exec中調(diào)用的normalizeExecArgs方法會(huì)將opts的shell屬性默認(rèn)設(shè)置為true。

exports.exec = function exec(/* command , options, callback */) {
 const opts = normalizeExecArgs.apply(null, arguments);
 return exports.execFile(opts.file, opts.options, opts.callback);
};

function normalizeExecArgs(command, options, callback) {
 options = { ...options };
 options.shell = typeof options.shell === 'string' ? options.shell : true;
 return { options };
}

在execFile中,最終調(diào)用的是spawn方法。

exports.execFile = function execFile(file /* , args, options, callback */) {
 let args = [];
 let callback;
 let options;
 var child = spawn(file, args, {
  // ... some options
 });
 
 return child;
}

exec會(huì)將spawn的輸入輸出流轉(zhuǎn)換成String,默認(rèn)使用UTF-8的編碼,然后傳遞給回調(diào)函數(shù),使用回調(diào)方式在node中較為熟悉,比流更容易操作,所以我們能使用exec方法執(zhí)行一些shell命令,然后在回調(diào)中獲取返回值。有點(diǎn)需要注意,這里的buffer是有最大緩存區(qū)的,如果超出會(huì)直接被kill掉,可用通過(guò)maxBuffer屬性進(jìn)行配置(默認(rèn): 200*1024)。

const { exec } = require('child_process');
exec('ls -lh /home', (error, stdout, stderr) => {
 console.log(`stdout: ${stdout}`);
 console.log(`stderr: ${stderr}`);
});

fork

fork最后也是調(diào)用spawn來(lái)創(chuàng)建子進(jìn)程,但是fork是spawn的一種特殊情況,用于衍生新的 Node.js 進(jìn)程,會(huì)產(chǎn)生一個(gè)新的V8實(shí)例,所以執(zhí)行fork方法時(shí)需要指定一個(gè)js文件。

exports.fork = function fork(modulePath /* , args, options */) {
 // ...
 
 options.shell = false;

 return spawn(options.execPath, args, options);
};

通過(guò)fork創(chuàng)建子進(jìn)程之后,父子進(jìn)程直接會(huì)創(chuàng)建一個(gè)IPC(進(jìn)程間通信)通道,方便父子進(jìn)程直接通信,在js層使用 process.send(message)process.on('message', msg => {}) 進(jìn)行通信。而在底層,實(shí)現(xiàn)進(jìn)程間通信的方式有很多,Node的進(jìn)程間通信基于libuv實(shí)現(xiàn),不同操作系統(tǒng)實(shí)現(xiàn)方式不一致。在*unix系統(tǒng)中采用Unix Domain Socket方式實(shí)現(xiàn),Windows中使用命名管道的方式實(shí)現(xiàn)。

常見(jiàn)進(jìn)程間通信方式:消息隊(duì)列、共享內(nèi)存、pipe、信號(hào)量、套接字

下面是一個(gè)父子進(jìn)程通信的實(shí)例。

parent.js

const path = require('path')
const { fork } = require('child_process')

const child = fork(path.join(__dirname, 'child.js'))

child.on('message', msg => {
  console.log('message from child', msg)
});

child.send('hello child, I\'m master')

child.js

process.on('message', msg => {
 console.log('message from master:', msg)
});
let counter = 0
setInterval(() => {
 process.send({
  child: true,
  counter: counter++
 })
}, 1000);

使用Node.js怎么實(shí)現(xiàn)進(jìn)程管理

小結(jié)

其實(shí)可以看到,這些方法都是對(duì)spawn方法的復(fù)用,然后spawn方法底層調(diào)用了libuv進(jìn)行進(jìn)程的管理,具體可以看下圖。

使用Node.js怎么實(shí)現(xiàn)進(jìn)程管理

利用fork實(shí)現(xiàn)master-worker模型

首先來(lái)看看,如果我們?cè)?code>child.js中啟動(dòng)一個(gè)http服務(wù)會(huì)發(fā)生什么情況。

// master.js
const { fork } = require('child_process')

for (let i = 0; i < 2; i++) {
 const child = fork('./child.js')
}

// child.js
const http = require('http')
http.createServer((req, res) => {
 res.end('Hello World\n');
}).listen(8000)

使用Node.js怎么實(shí)現(xiàn)進(jìn)程管理

   +--------------+
       |       |
       |  master  |
       |       |
   +--------+--------------+- -- -- -
   |                 |
   |             Error: listen EADDRINUSE
   |                 |
   |
+----v----+           +-----v---+
|     |           |     |
| worker1 |           | worker2 |
|     |           |     |
+---------+           +---------+
  :8000              :8000

我們fork了兩個(gè)子進(jìn)程,因?yàn)閮蓚€(gè)子進(jìn)程同時(shí)對(duì)一個(gè)端口進(jìn)行監(jiān)聽(tīng),Node會(huì)直接拋出一個(gè)異常(Error: listen EADDRINUSE),如上圖所示。那么我們能不能使用代理模式,同時(shí)監(jiān)聽(tīng)多個(gè)端口,讓master進(jìn)程監(jiān)聽(tīng)80端口收到請(qǐng)求時(shí),再將請(qǐng)求分發(fā)給不同服務(wù),而且master進(jìn)程還能做適當(dāng)?shù)?a title="負(fù)載均衡" target="_blank" >負(fù)載均衡。

   +--------------+
       |       |
       |  master  |
       |   :80   |
   +--------+--------------+---------+
   |                 |
   |                 |
   |                 |
   |                 |
+----v----+           +-----v---+
|     |           |     |
| worker1 |           | worker2 |
|     |           |     |
+---------+           +---------+
  :8000              :8001

但是這么做又會(huì)帶來(lái)另一個(gè)問(wèn)題,代理模式中十分消耗文件描述符(linux系統(tǒng)默認(rèn)的最大文件描述符限制是1024),文件描述符在windows系統(tǒng)中稱為句柄(handle),習(xí)慣性的我們也可以稱linux中的文件描述符為句柄。當(dāng)用戶進(jìn)行訪問(wèn),首先連接到master進(jìn)程,會(huì)消耗一個(gè)句柄,然后master進(jìn)程再代理到worker進(jìn)程又會(huì)消耗掉一個(gè)句柄,所以這種做法十分浪費(fèi)系統(tǒng)資源。為了解決這個(gè)問(wèn)題,Node的進(jìn)程間通信可以發(fā)送句柄,節(jié)省系統(tǒng)資源。

句柄是一種特殊的智能指針 。當(dāng)一個(gè)應(yīng)用程序要引用其他系統(tǒng)(如數(shù)據(jù)庫(kù)、操作系統(tǒng))所管理的內(nèi)存塊或?qū)ο髸r(shí),就要使用句柄。

我們可以在master進(jìn)程啟動(dòng)一個(gè)tcp服務(wù),然后通過(guò)IPC將服務(wù)的句柄發(fā)送給子進(jìn)程,子進(jìn)程再對(duì)服務(wù)的連接事件進(jìn)行監(jiān)聽(tīng),具體代碼如下:

// master.js
var { fork } = require('child_process')
var server = require('net').createServer()
server.on('connection', function(socket) {
 socket.end('handled by master') // 響應(yīng)來(lái)自master
})
server.listen(3000, function() {
 console.log('master listening on: ', 3000)
})
for (var i = 0; i < 2; i++) {
 var child = fork('./child.js')
 child.send('server', server) // 發(fā)送句柄給worker
 console.log('worker create, pid is ', child.pid)
}

// child.js
process.on('message', function (msg, handler) {
 if (msg !== 'server') {
  return
 }
 // 獲取到句柄后,進(jìn)行請(qǐng)求的監(jiān)聽(tīng)
 handler.on('connection', function(socket) {
  socket.end('handled by worker, pid is ' + process.pid) 
 })
})

使用Node.js怎么實(shí)現(xiàn)進(jìn)程管理

下面我們通過(guò)curl連續(xù)請(qǐng)求 5 次服務(wù)。

for varible1 in {1..5}
do
 curl "localhost:3000"
done

使用Node.js怎么實(shí)現(xiàn)進(jìn)程管理

可以看到,響應(yīng)請(qǐng)求的可以是父進(jìn)程,也可以是不同子進(jìn)程,多個(gè)進(jìn)程對(duì)同一個(gè)服務(wù)響應(yīng)的連接事件監(jiān)聽(tīng),誰(shuí)先搶占,就由誰(shuí)進(jìn)行響應(yīng)。這里就會(huì)出現(xiàn)一個(gè)Linux網(wǎng)絡(luò)編程中很常見(jiàn)的事件,當(dāng)多個(gè)進(jìn)程同時(shí)監(jiān)聽(tīng)網(wǎng)絡(luò)的連接事件,當(dāng)這個(gè)有新的連接到達(dá)時(shí),這些進(jìn)程被同時(shí)喚醒,這被稱為“驚群”。這樣導(dǎo)致的情況就是,一旦事件到達(dá),每個(gè)進(jìn)程同時(shí)去響應(yīng)這一個(gè)事件,而最終只有一個(gè)進(jìn)程能處理事件成功,其他的進(jìn)程在處理該事件失敗后重新休眠,造成了系統(tǒng)資源的浪費(fèi)。

使用Node.js怎么實(shí)現(xiàn)進(jìn)程管理

ps:在windows系統(tǒng)上,永遠(yuǎn)都是最后定義的子進(jìn)程搶占到句柄,這可能和libuv的實(shí)現(xiàn)機(jī)制有關(guān),具體原因往有大佬能夠指點(diǎn)。

使用Node.js怎么實(shí)現(xiàn)進(jìn)程管理

出現(xiàn)這樣的問(wèn)題肯定是大家都不愿意的嘛,這個(gè)時(shí)候我們就想起了nginx的好了,這里有篇文章講解了nginx是如何解決“驚群”的,利用nginx的反向代理可以有效地解決這個(gè)問(wèn)題,畢竟nginx本來(lái)就很擅長(zhǎng)這種問(wèn)題。

http { 
 upstream node { 
   server 127.0.0.1:8000; 
   server 127.0.0.1:8001; 
   server 127.0.0.1:8002; 
   server 127.0.0.1:8003;
   keepalive 64;
 } 
 server { 
    listen 80; 
    server_name shenfq.com; 
    location / { 
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header Host $http_host;
      proxy_set_header X-Nginx-Proxy true;
      proxy_set_header Connection "";
      proxy_pass http://node; # 這里要和最上面upstream后的應(yīng)用名一致,可以自定義
    } 
 }
}

小結(jié)

如果我們自己用Node原生來(lái)實(shí)現(xiàn)一個(gè)多進(jìn)程模型,存在這樣或者那樣的問(wèn)題,雖然最終我們借助了nginx達(dá)到了這個(gè)目的,但是使用nginx的話,我們需要另外維護(hù)一套nginx的配置,而且如果有一個(gè)Node服務(wù)掛了,nginx并不知道,還是會(huì)將請(qǐng)求轉(zhuǎn)發(fā)到那個(gè)端口。

cluster模塊

除了用nginx做反向代理,node本身也提供了一個(gè)cluster模塊,用于多核CPU環(huán)境下多進(jìn)程的負(fù)載均衡。cluster模塊創(chuàng)建子進(jìn)程本質(zhì)上是通過(guò)child_procee.fork,利用該模塊可以很容易的創(chuàng)建共享同一端口的子進(jìn)程服務(wù)器

上手指南

有了這個(gè)模塊,你會(huì)感覺(jué)實(shí)現(xiàn)Node的單機(jī)集群是多么容易的一件事情。下面看看官方實(shí)例,短短的十幾行代碼就實(shí)現(xiàn)了一個(gè)多進(jìn)程的Node服務(wù),且自帶負(fù)載均衡。

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

if (cluster.isMaster) { // 判斷是否為主進(jìn)程
 console.log(`主進(jìn)程 ${process.pid} 正在運(yùn)行`);

 // 衍生工作進(jìn)程。
 for (let i = 0; i < numCPUs; i++) {
  cluster.fork();
 }

 cluster.on('exit', (worker, code, signal) => {
  console.log(`工作進(jìn)程 ${worker.process.pid} 已退出`);
 });
} else { // 子進(jìn)程進(jìn)行服務(wù)器創(chuàng)建
 // 工作進(jìn)程可以共享任何 TCP 連接。
 // 在本例子中,共享的是一個(gè) HTTP 服務(wù)器。
 http.createServer((req, res) => {
  res.writeHead(200);
  res.end('hello world\n');
 }).listen(8000);

 console.log(`工作進(jìn)程 ${process.pid} 已啟動(dòng)`);
}

使用Node.js怎么實(shí)現(xiàn)進(jìn)程管理

cluster模塊源碼分析

首先看代碼,通過(guò)isMaster來(lái)判斷是否為主進(jìn)程,如果是主進(jìn)程進(jìn)行fork操作,子進(jìn)程創(chuàng)建服務(wù)器。這里cluster進(jìn)行fork操作時(shí),執(zhí)行的是當(dāng)前文件。cluster.fork最終調(diào)用的child_process.fork,且第一個(gè)參數(shù)為process.argv.slice(2),在fork子進(jìn)程之后,會(huì)對(duì)其internalMessage事件進(jìn)行監(jiān)聽(tīng),這個(gè)后面會(huì)提到,具體代碼如下:

const { fork } = require('child_process');

cluster.fork = function(env) {
 cluster.setupMaster();
 const id = ++ids;
 const workerProcess = createWorkerProcess(id, env);
 const worker = new Worker({
  id: id,
  process: workerProcess
 });
 
 // 監(jiān)聽(tīng)子進(jìn)程的消息
 worker.process.on('internalMessage', internal(worker, onmessage));
 // ...
};
// 配置master進(jìn)程
cluster.setupMaster = function(options) {
 cluster.settings = {
  args: process.argv.slice(2),
  exec: process.argv[1],
  execArgv: process.execArgv,
  silent: false,
  ...cluster.settings,
  ...options
 };
};

// 創(chuàng)建子進(jìn)程
function createWorkerProcess(id, env) {
 return fork(cluster.settings.exec, cluster.settings.args, {
  // some options
 });
}

子進(jìn)程端口監(jiān)聽(tīng)問(wèn)題

這里會(huì)有一個(gè)問(wèn)題,子進(jìn)程全部都在監(jiān)聽(tīng)同一個(gè)端口,我們之前已經(jīng)試驗(yàn)過(guò),服務(wù)監(jiān)聽(tīng)同一個(gè)端口會(huì)出現(xiàn)端口占用的問(wèn)題,那么cluster模塊如何保證端口不沖突的呢? 查閱源碼發(fā)現(xiàn),http模塊的createServer繼承自net模塊。

util.inherits(Server, net.Server);

而在net模塊中,listen方法會(huì)調(diào)用listenInCluster方法,listenInCluster判斷當(dāng)前是否為master進(jìn)程。

lib/net.js

Server.prototype.listen = function(...args) {

 // ...
 if (typeof options.port === 'number' || typeof options.port === 'string') {
  // 如果listen方法只傳入了端口號(hào),最后會(huì)走到這里
  listenInCluster(this, null, options.port | 0, 4, backlog, undefined, options.exclusive);
  return this;
 }
 // ...
};

function listenInCluster(server, address, port, addressType, backlog, fd, exclusive, flags) {
 if (cluster === undefined) cluster = require('cluster');

 if (cluster.isMaster) {
  // 如果是主進(jìn)程則啟動(dòng)一個(gè)服務(wù)
  // 但是主進(jìn)程沒(méi)有調(diào)用過(guò)listen方法,所以沒(méi)有走這里一步
  server._listen2(address, port, addressType, backlog, fd, flags);
  return;
 }
 
 const serverQuery = {
  address: address,
  port: port,
  addressType: addressType,
  fd: fd,
  flags,
 };
 
 // 子進(jìn)程獲取主進(jìn)程服務(wù)的句柄
 cluster._getServer(server, serverQuery, listenOnMasterHandle);
 
 function listenOnMasterHandle(err, handle) {
  server._handle = handle; // 重寫handle,對(duì)listen方法進(jìn)行了hack
  server._listen2(address, port, addressType, backlog, fd, flags);
 }
}

看上面代碼可以知道,真正啟動(dòng)服務(wù)的方法為server._listen2。在_listen2方法中,最終調(diào)用的是_handle下的listen方法。

function setupListenHandle(address, port, addressType, backlog, fd, flags) {
 // ...
 this._handle.onconnection = onconnection;
 var err = this._handle.listen(backlog || 511);
 // ...
}

Server.prototype._listen2 = setupListenHandle; // legacy alias

那么cluster._getServer方法到底做了什么呢?

搜尋它的源碼,首先向master進(jìn)程發(fā)送了一個(gè)消息,消息類型為queryServer

// child.js
cluster._getServer = function(obj, options, cb) {
 // ...
 
 const message = {
  act: 'queryServer',
  index,
  data: null,
  ...options
 };
 
 // 發(fā)送消息到master進(jìn)程,消息類型為 queryServer
 send(message, (reply, handle) => {
  rr(reply, indexesKey, cb);       // Round-robin.
 });
 // ...
};

這里的rr方法,對(duì)前面提到的_handle.listen進(jìn)行了hack,所有子進(jìn)程的listen其實(shí)是不起作用的。

function rr(message, indexesKey, cb) {
 if (message.errno)
  return cb(message.errno, null);

 var key = message.key;

 function listen(backlog) { // listen方法直接返回0,不再進(jìn)行端口監(jiān)聽(tīng)
  return 0;
 }

 function close() {
  send({ act: 'close', key });
 }

 function getsockname(out) {
  return 0;
 }
 
 const handle = { close, listen, ref: noop, unref: noop };
 
 handles.set(key, handle); // 根據(jù)key將工作進(jìn)程的 handle 進(jìn)行緩存
 cb(0, handle);
}

// 這里的cb回調(diào)就是前面_getServer方法傳入的。 參考之前net模塊的listen方法
function listenOnMasterHandle(err, handle) {
 server._handle = handle; // 重寫handle,對(duì)listen方法進(jìn)行了hack
 // 該方法調(diào)用后,會(huì)對(duì)handle綁定一個(gè) onconnection 方法,最后會(huì)進(jìn)行調(diào)用
 server._listen2(address, port, addressType, backlog, fd, flags);
}

主進(jìn)程與子進(jìn)程通信

那么到底在哪里對(duì)端口進(jìn)行了監(jiān)聽(tīng)呢?

前面提到過(guò),fork子進(jìn)程的時(shí)候,對(duì)子進(jìn)程進(jìn)行了internalMessage事件的監(jiān)聽(tīng)。

worker.process.on('internalMessage', internal(worker, onmessage));

子進(jìn)程向master進(jìn)程發(fā)送消息,一般使用process.send方法,會(huì)被監(jiān)聽(tīng)的message事件所接收。這里是因?yàn)榘l(fā)送的message指定了cmd: 'NODE_CLUSTER',只要cmd字段以NODE_開(kāi)頭,這樣消息就會(huì)認(rèn)為是內(nèi)部通信,被internalMessage事件所接收。

// child.js
function send(message, cb) {
 return sendHelper(process, message, null, cb);
}

// utils.js
function sendHelper(proc, message, handle, cb) {
 if (!proc.connected)
  return false;

 // Mark message as internal. See INTERNAL_PREFIX in lib/child_process.js
 message = { cmd: 'NODE_CLUSTER', ...message, seq };

 if (typeof cb === 'function')
  callbacks.set(seq, cb);

 seq += 1;
 return proc.send(message, handle);
}

master進(jìn)程接收到消息后,根據(jù)act的類型開(kāi)始執(zhí)行不同的方法,這里act為queryServer。queryServer方法會(huì)構(gòu)造一個(gè)key,如果這個(gè)key(規(guī)則主要為地址+端口+文件描述符)之前不存在,則對(duì)RoundRobinHandle構(gòu)造函數(shù)進(jìn)行了實(shí)例化,RoundRobinHandle構(gòu)造函數(shù)中啟動(dòng)了一個(gè)TCP服務(wù),并對(duì)之前指定的端口進(jìn)行了監(jiān)聽(tīng)。

// master.js
const handles = new Map();

function onmessage(message, handle) {
 const worker = this;
 if (message.act === 'online')
  online(worker);
 else if (message.act === 'queryServer')
  queryServer(worker, message);
 // other act logic
}
function queryServer(worker, message) {
 // ...
 const key = `${message.address}:${message.port}:${message.addressType}:` +
       `${message.fd}:${message.index}`;
 var handle = handles.get(key);
 // 如果之前沒(méi)有對(duì)該key進(jìn)行實(shí)例化,則進(jìn)行實(shí)例化
 if (handle === undefined) {
  let address = message.address;
  // const RoundRobinHandle = require('internal/cluster/round_robin_handle');
  var constructor = RoundRobinHandle;

  handle = new constructor(key,
               address,
               message.port,
               message.addressType,
               message.fd,
               message.flags);
  handles.set(key, handle);
 }
 // ...
}

// internal/cluster/round_robin_handle
function RoundRobinHandle(key, address, port, addressType, fd, flags) {
 this.server = net.createServer(assert.fail);
 // 這里啟動(dòng)一個(gè)TCP服務(wù)器
 this.server.listen({ port, host });
 
 // TCP服務(wù)器啟動(dòng)時(shí)的事件
 this.server.once('listening', () => {
  this.handle = this.server._handle;
  this.handle.onconnection = (err, handle) => this.distribute(err, handle);
 });
 // ...
}

可以看到TCP服務(wù)啟動(dòng)后,立馬對(duì)connection事件進(jìn)行了監(jiān)聽(tīng),會(huì)調(diào)用RoundRobinHandle的distribute方法。

// RoundRobinHandle
this.handle.onconnection = (err, handle) => this.distribute(err, handle);

// distribute 對(duì)工作進(jìn)程進(jìn)行分發(fā)
RoundRobinHandle.prototype.distribute = function(err, handle) {
 this.handles.push(handle); // 存入TCP服務(wù)的句柄
 const worker = this.free.shift(); // 取出第一個(gè)工作進(jìn)程

 if (worker)
  this.handoff(worker); // 切換到工作進(jìn)程
};

RoundRobinHandle.prototype.handoff = function(worker) {
 const handle = this.handles.shift(); // 獲取TCP服務(wù)句柄
 
 if (handle === undefined) {
  this.free.push(worker); // 將該工作進(jìn)程重新放入隊(duì)列中
  return;
 }
 
 const message = { act: 'newconn', key: this.key };

 // 向工作進(jìn)程發(fā)送一個(gè)類型為 newconn 的消息以及TCP服務(wù)的句柄
 sendHelper(worker.process, message, handle, (reply) => {
  if (reply.accepted)
   handle.close();
  else
   this.distribute(0, handle); // 工作進(jìn)程不能正常運(yùn)行,啟動(dòng)下一個(gè)

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

在子進(jìn)程中也有對(duì)內(nèi)部消息進(jìn)行監(jiān)聽(tīng),在cluster/child.js中,有個(gè)cluster._setupWorker方法,該方法會(huì)對(duì)內(nèi)部消息監(jiān)聽(tīng),該方法的在lib/internal/bootstrap/node.js中調(diào)用,這個(gè)文件是每次啟動(dòng)node命令后,由C++模塊調(diào)用的。

鏈接

function startup() {
 // ...
 startExecution();
}
function startExecution() {
 // ...
 prepareUserCodeExecution();
}
function prepareUserCodeExecution() {
 if (process.argv[1] && process.env.NODE_UNIQUE_ID) {
  const cluster = NativeModule.require('cluster');
  cluster._setupWorker();
  delete process.env.NODE_UNIQUE_ID;
 }
}

startup()

下面看看_setupWorker方法做了什么。

cluster._setupWorker = function() {
 // ...
 process.on('internalMessage', internal(worker, onmessage));

 function onmessage(message, handle) {
  // 如果act為 newconn 調(diào)用onconnection方法
  if (message.act === 'newconn')
   onconnection(message, handle);
  else if (message.act === 'disconnect')
   _disconnect.call(worker, true);
 }
};

function onconnection(message, handle) {
 const key = message.key;
 const server = handles.get(key);
 const accepted = server !== undefined;

 send({ ack: message.seq, accepted });

 if (accepted)
  server.onconnection(0, handle); // 調(diào)用net中的onconnection方法
}

上述就是小編為大家分享的使用Node.js怎么實(shí)現(xiàn)進(jìn)程管理了,如果剛好有類似的疑惑,不妨參照上述分析進(jìn)行理解。如果想知道更多相關(guān)知識(shí),歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。

新聞名稱:使用Node.js怎么實(shí)現(xiàn)進(jìn)程管理
分享鏈接:http://muchs.cn/article20/ighjjo.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供營(yíng)銷型網(wǎng)站建設(shè)、商城網(wǎng)站、網(wǎng)頁(yè)設(shè)計(jì)公司、靜態(tài)網(wǎng)站、手機(jī)網(wǎng)站建設(shè)、網(wǎng)站內(nèi)鏈

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

搜索引擎優(yōu)化