" />

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

深入了解 Node的多進(jìn)程服務(wù)實(shí)現(xiàn)

 更新時(shí)間:2022年06月06日 15:10:19   作者:全棧私房菜  
本文主要介紹了Node的多進(jìn)程服務(wù)實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

我們現(xiàn)在已經(jīng)知道了Node是單線程運(yùn)行的,這表示潛在的錯(cuò)誤有可能導(dǎo)致線程崩潰,然后進(jìn)程也會(huì)隨著退出,無(wú)法做到企業(yè)追求的穩(wěn)定性;另一方面,單進(jìn)程也無(wú)法充分多核CPU,這是對(duì)硬件本身的浪費(fèi)。Node社區(qū)本身也意識(shí)到了這一問(wèn)題,于是從0.1版本就提供了child_process模塊,用來(lái)提供多進(jìn)程的支持。

1. child_process 模塊

child_process模塊中包括了很多創(chuàng)建子進(jìn)程的方法,包括fork、spawn、exec、execFile等等。它們的定義如下:

  • child_process.exec(command[, options][, callback])
  • child_process.spawn(command[, args][, options])
  • child_process.fork(modulePath[, args][, options])
  • child_process.execFile(file[, args][, options][, callback])

在這4個(gè)API中以spawn最為基礎(chǔ),因?yàn)槠渌齻€(gè)API或多或少都是借助spawn實(shí)現(xiàn)的。

2. spawn

spawn方法的聲明格式如下:

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

spawn方法會(huì)使用指定的command來(lái)生成一個(gè)新進(jìn)程,執(zhí)行完對(duì)應(yīng)的command后子進(jìn)程會(huì)自動(dòng)退出。

該命令返回一個(gè)child_process對(duì)象,這代表開(kāi)發(fā)者可以通過(guò)監(jiān)聽(tīng)事件來(lái)獲得命令執(zhí)行的結(jié)果。

下面我們使用spwan來(lái)執(zhí)行ls命令:

const spawn = require('child_process').spawn;
const ls = spawn('ls', ['-1h', '/usr']);

ls.stdout.on('data', (data) => {
    console.log('stdout: ', daata.toString());
});

ls.stderr.on('data', (data) => {
    console.log('stderr: ', daata.toString());
});

ls.on('close', (code) => {
    console.log('child process exited with code', code);
});

其中spawn的第一個(gè)參數(shù)雖然是command,但實(shí)際接收的卻是一個(gè)file,可以在Linux或者M(jìn)ac OSX上運(yùn)行,這是由于ls命令也是以可執(zhí)行文件形式存在的。

類(lèi)似的,在Windows系統(tǒng)下我們可以試著使用dir命令來(lái)實(shí)現(xiàn)功能類(lèi)似的代碼:

const spawn = require('child_process').spawn;
const ls = spawn('dir');

ls.stdout.on('data', (data) => {
    console.log('stdout: ', daata.toString());
});

然而在Windows下執(zhí)行上面代碼會(huì)出現(xiàn)形如Error:spawn dir ENOENT的錯(cuò)誤。

原因就在于spawn實(shí)際接收的是一個(gè)文件名而非命令,正確的代碼如下:

const spawn = require('child_process').spawn;
const ls = spawn('powershell', ['dir']);

ls.stdout.on('data', (data) => {
    console.log('stdout: ', daata.toString());
});

這個(gè)問(wèn)題的原因與操作系統(tǒng)本身有關(guān),在Linux中,一般都是文件,命令行的命令也不例外,例如ls命令是一個(gè)名為ls的可執(zhí)行文件;而在Windows中并沒(méi)有名為dir的可執(zhí)行文件,需要通過(guò)cmd或者powershell之類(lèi)的工具提供執(zhí)行環(huán)境。

3. fork

在Linux環(huán)境下,創(chuàng)建一個(gè)新進(jìn)程的本質(zhì)是復(fù)制一個(gè)當(dāng)前的進(jìn)程,當(dāng)用戶調(diào)用 fork 后,操作系統(tǒng)會(huì)先為這個(gè)新進(jìn)程分配空間,然后將父進(jìn)程的數(shù)據(jù)原樣復(fù)制一份過(guò)去,父進(jìn)程和子進(jìn)程只有少數(shù)值不同,例如進(jìn)程標(biāo)識(shí)符(PD)。

對(duì)于 Node 來(lái)說(shuō),父進(jìn)程和子進(jìn)程都有獨(dú)立的內(nèi)存空間和獨(dú)立的 V8 實(shí)例,它們和父進(jìn)程唯一的聯(lián)系是用來(lái)進(jìn)程間通信的 IPC Channel。

此外,Node中fork和 POSIX 系統(tǒng)調(diào)用的不同之處在于Node中的fork并不會(huì)復(fù)制父進(jìn)程。

Node中的fork是上面提到的spawn的一種特例,前面也提到了Node中的fork并不會(huì)復(fù)制當(dāng)前進(jìn)程。多數(shù)情況下,fork接收的第一個(gè)參數(shù)是一個(gè)文件名,使用fork("xx.js")相當(dāng)于在命令行下調(diào)用node xx.js,并且父進(jìn)程和子進(jìn)程之間可以通過(guò)process.send方法來(lái)進(jìn)行通信。

下面我們來(lái)看一個(gè)簡(jiǎn)單的栗子:

// master.js 調(diào)用 fork 來(lái)創(chuàng)建一個(gè)子進(jìn)程
const child_process = require('child_process');
const worker = child_process.fork('worker.js', ['args1']);
worker.on('exit', () => {
  console.log('child process exit');
});
worker.send({ msg: 'hello child' });
worker.on('message', msg => {
  console.log('from child: ', msg);
});


// worker.js
const begin = process.argv[2];
console.log('I am worker ' + begin);
process.on('message', msg => {
  console.log('from parent ', msg);
  process.exit();
});
process.send({ msg: 'hello parent' });

fork內(nèi)部會(huì)通過(guò)spawn調(diào)用process.executePath,即Node的可執(zhí)行文件地址來(lái)生成一個(gè)Node實(shí)例,然后再用這個(gè)實(shí)例來(lái)執(zhí)行fork方法的modulePath參數(shù)。

輸出結(jié)果為:

I am worker args1
from parent  { msg: 'hello child' }
from child:  { msg: 'hello parent' }
child process exit

4. exec 和 execFile

如果我們開(kāi)發(fā)一種系統(tǒng),那么對(duì)于不同的模塊可能會(huì)用到不同的技術(shù)來(lái)實(shí)現(xiàn),例如 Web服務(wù)器使用 Node ,然后再使用 Java 的消息隊(duì)列提供發(fā)布訂閱服務(wù),這種情況下通常使用進(jìn)程間通信的方式來(lái)實(shí)現(xiàn)。

但有時(shí)開(kāi)發(fā)者不希望使用這么復(fù)雜的方式,或者要調(diào)用的干脆是一個(gè)黑盒系統(tǒng),即無(wú)法通過(guò)修改源碼來(lái)進(jìn)行來(lái)實(shí)現(xiàn)進(jìn)程間通信,這時(shí)候往往采用折中的方式,例如通過(guò) shell 來(lái)調(diào)用目標(biāo)服務(wù),然后再拿到對(duì)應(yīng)的輸出。

child_process提供了一個(gè)execFile方法,它的聲明如下:

child_process.execFile(file, args, options, callback) 

說(shuō)明:

  • file {String}要運(yùn)行的程序的文件名
  • args {Array}字符串參數(shù)列表
  • options {Object}
    • cwd {String}子進(jìn)程的當(dāng)前工作目錄
    • env {Object}環(huán)境變量鍵值對(duì)
    • encoding {String}編碼(默認(rèn)為 'utf8'
    • timeout {Number}超時(shí)(默認(rèn)為 0)
    • maxBuffer {Number}緩沖區(qū)大小(默認(rèn)為 200*1024)
    • killSignal {String}結(jié)束信號(hào)(默認(rèn)為'SIGTERM'
  • callback {Function}進(jìn)程結(jié)束時(shí)回調(diào)并帶上輸出
    • error {Error}
    • stdout {Buffer}
    • stderr {Buffer}
    • 返回:ChildProcess對(duì)象

可以看出,execfilespawn在形式上的主要區(qū)別在于execfile提供了一個(gè)回調(diào)函數(shù),通過(guò)這個(gè)回調(diào)函數(shù)可以獲得子進(jìn)程的標(biāo)準(zhǔn)輸出/錯(cuò)誤流。

使用 shell 進(jìn)行跨進(jìn)程調(diào)用長(zhǎng)久以來(lái)被認(rèn)為是不穩(wěn)定的,這大概源于人們對(duì)控制臺(tái)不友好的交互體驗(yàn)的恐懼(輸入命令后,很可能長(zhǎng)時(shí)間看不到一個(gè)輸出,盡管后臺(tái)可能在一直運(yùn)算,但在用戶看來(lái)和死機(jī)無(wú)異)。

在 Linux下執(zhí)行exec命令后,原有進(jìn)程會(huì)被替換成新的進(jìn)程,進(jìn)而失去對(duì)新進(jìn)程的控制,這代表著新進(jìn)程的狀態(tài)也沒(méi)辦法獲取了,此外還有 shell 本身運(yùn)行出現(xiàn)錯(cuò)誤,或者因?yàn)楦鞣N原因出現(xiàn)長(zhǎng)時(shí)間卡頓甚至失去響應(yīng)等情況。

Node.js 提供了比較好的解決方案,timeout解決了長(zhǎng)時(shí)間卡頓的問(wèn)題,stdoutstderr則提供了標(biāo)準(zhǔn)輸出和錯(cuò)誤輸出,使得子進(jìn)程的狀態(tài)可以被獲取。

5. 各方法之間的比較

5.1 spawn 和 execFile

為了更好地說(shuō)明,我們先寫(xiě)一段簡(jiǎn)單的 C 語(yǔ)言代碼,并將其命名為 example.c:

#include<stdio.h>
int main() {
    printf("%s", "Hello World!");
    return 5;
}

使用 gcc 編譯該文件:

gcc example.c -o example

生成名為example的可執(zhí)行文件,然后將這個(gè)可執(zhí)行文件放到系統(tǒng)環(huán)境變量中,然后打開(kāi)控制臺(tái),輸入example,看到最后輸出"Hello World"。

確保這個(gè)可執(zhí)行文件在任意路徑下都能訪問(wèn)。

我們分別用spawnexecfile來(lái)調(diào)用example文件。

首先是spawn。

const spawn = require('child_process').spawn;
const ls = spawn('example');

ls.stdout.on('data', (data) => {
    console.log('stdout: ', daata.toString());
});

ls.stderr.on('data', (data) => {
    console.log('stderr: ', daata.toString());
});

ls.on('close', (code) => {
    console.log('child process exited with code', code);
});

程序輸出:

stdout: Hello World!
child process exited with code 5

程序正確打印出了Hello World,此外還可以看到example最后的return 5會(huì)被作為子進(jìn)程結(jié)束的code被返回。

然后是execFile

const exec = require('child_process').exec;
const child = exec('example', (error, stdout, stderr) => {
    if (error) {
        throw error;
    }
    console.log(stdout);
});

同樣打印出Hello World,可見(jiàn)除了調(diào)用形式不同,二者相差不大。

5.2 execFile 和 spawn

在子進(jìn)程的信息交互方面,spawn使用了流式處理的方式,當(dāng)子進(jìn)程產(chǎn)生數(shù)據(jù)時(shí),主進(jìn)程可以通過(guò)監(jiān)聽(tīng)事件來(lái)獲取消息;而exec是將所有返回的信息放在stdout里面一次性返回的,也就是該方法的maxBuffer參數(shù),當(dāng)子進(jìn)程的輸出超過(guò)這個(gè)大小時(shí),會(huì)產(chǎn)生一個(gè)錯(cuò)誤。

此外,spawn有一個(gè)名為shell的參數(shù):

其類(lèi)型為一個(gè)布爾值或者字符串,如果這個(gè)值被設(shè)置為true,,就會(huì)啟動(dòng)一個(gè) shell 來(lái)執(zhí)行命令,這個(gè) shell 在 UNIX上是 bin/sh,,在Windows上則是cmd.exe。

5.3 exec 和 execFile

exec在內(nèi)部也是通過(guò)調(diào)用execFile來(lái)實(shí)現(xiàn)的,我們可以從源碼中驗(yàn)證這一點(diǎn),在早期的Node源碼中,exec命令會(huì)根據(jù)當(dāng)前環(huán)境來(lái)初始化一個(gè) shell,,例如 cmd.exe 或者 bin/sh,然后在shell中調(diào)用作為參數(shù)的命令。

通常execFile的效率要高于exec,這是因?yàn)?code>execFile沒(méi)有啟動(dòng)一個(gè) shell,而是直接調(diào)用 spawn來(lái)實(shí)現(xiàn)的。

6. 進(jìn)程間通信

前面介紹的幾個(gè)用于創(chuàng)建進(jìn)程的方法,都是屬于child_process的類(lèi)方法,此外childProcess類(lèi)繼承了EventEmitter,在childProcess中引入事件給進(jìn)程間通信帶來(lái)很大的便利。

childProcess中定義了如下事件。

  • Event:'close':進(jìn)程的輸入輸出流關(guān)閉時(shí)會(huì)觸發(fā)該事件。
  • Event:'disconnect':通常childProcess.disconnect調(diào)用后會(huì)觸發(fā)這一事件。
  • Event:'exit':進(jìn)程退出時(shí)觸發(fā)。
  • Event:'message':調(diào)用child_process.send會(huì)觸發(fā)這一事件
  • Event:'error':該事件的觸發(fā)分為幾種情況:
    • 該進(jìn)程無(wú)法創(chuàng)建子進(jìn)程。
    • 該進(jìn)程無(wú)法通過(guò)kill方法關(guān)閉。
    • 無(wú)法發(fā)送消息給子進(jìn)程。

Event:'error'事件無(wú)法保證一定會(huì)被觸發(fā),因?yàn)榭赡軙?huì)遇到一些極端情況,例如服務(wù)器斷電等。

上面也提到,childProcess模塊定義了send方法,用于進(jìn)程間通信,該方法的聲明如下:

child.send(message[, sendHandle[, options]][, callback])

通過(guò)send方法發(fā)送的消息,可以通過(guò)監(jiān)聽(tīng)message事件來(lái)獲取。

// master.js 父進(jìn)程向子進(jìn)程發(fā)送消息
const child_process = require('child_process');
const worker = child_process.fork('worker.js', ['args1']);
worker.on('exit', () => {
  console.log('child process exit');
});
worker.send({ msg: 'hello child' });
worker.on('message', msg => {
  console.log('from child: ', msg);
});


// worker.js 子進(jìn)程接收父進(jìn)程消息
const begin = process.argv[2];
console.log('I am worker ' + begin);
process.on('message', msg => {
  console.log('from parent ', msg);
  process.exit();
});
process.send({ msg: 'hello parent' });

send方法的第一個(gè)參數(shù)類(lèi)型通常為一個(gè)json對(duì)象或者原始類(lèi)型,第二個(gè)參數(shù)是一個(gè)句柄,該句柄可以是一個(gè)net.Socket或者net.Server對(duì)象。下面是一個(gè)例子:

//master.js 父進(jìn)程發(fā)送一個(gè) Socket 對(duì)象
const child = require('child_process').fork('worker.js');
// Open up the server object and send the handle.
const server = require('net').createServer();
server.on('connection', socket => {
  socket.end('handled by parent');
});
server.listen(1337, () => {
  child.send('server', server);
});


//worker.js 子進(jìn)程接收 Socket 對(duì)象
process.on('message', (m, server) => {
  if (m === 'server') {
    server.on('connection', socket => {
      socket.end('handled by child');
    });
  }
});

7. Cluster

前面已經(jīng)介紹了child_process的使用,child_process的一個(gè)重要使用場(chǎng)景是創(chuàng)建多進(jìn)程服務(wù)來(lái)保證服務(wù)穩(wěn)定運(yùn)行。

為了統(tǒng)一 Node 創(chuàng)建多進(jìn)程服務(wù)的方式,Node 在之后的版本中增加了Cluster模塊,Cluster可以看作是做了封裝的child_Process模塊。

Cluster模塊的一個(gè)顯著優(yōu)點(diǎn)是可以共享同一個(gè)socket連接,這代表可以使用Cluster模塊實(shí)現(xiàn)簡(jiǎn)單的負(fù)載均衡。

下面是Cluster的簡(jiǎn)單栗子:

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

if (cluster.isMaster) {
  console.log('Master process id is', process.pid);
  // Fork workers.
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }
  cluster.on('exit', (worker, code, signal) => {
    console.log('worker process died, id ', worker.process.pid);
  });
} else {
  // Worker 可以共享同一個(gè) TCP 連接
  // 這里的例子是一個(gè) http 服務(wù)器
  http.createServer((req, res) => {
    res.writeHead(200);
    res.end('hello world\n');
  }).listen(8000);

  console.log('Worker started, process id', process.pid);
}

上面是使用Cluster模塊的一個(gè)簡(jiǎn)單的例子,為了充分利用多核CPU,先調(diào)用OS模塊的cpus()方法來(lái)獲得CPU的核心數(shù),假設(shè)主機(jī)裝有兩個(gè) CPU,每個(gè)CPU有4個(gè)核,那么總核數(shù)就是8。

在上面的代碼中,Cluster模塊調(diào)用fork方法來(lái)創(chuàng)建子進(jìn)程,該方法和child_process中的fork是同一個(gè)方法。

Cluster模塊采用的是經(jīng)典的主從模型,由master進(jìn)程來(lái)管理所有的子進(jìn)程,可以使用cluster.isMaster屬性判斷當(dāng)前進(jìn)程是master還是worker,其中主進(jìn)程不負(fù)責(zé)具體的任務(wù)處理,其主要工作是負(fù)責(zé)調(diào)度和管理,上面的代碼中,所有的子進(jìn)程都監(jiān)聽(tīng)8000端口。

通常情況下,如果多個(gè) Node 進(jìn)程監(jiān)聽(tīng)同一個(gè)端口時(shí)會(huì)出現(xiàn)Error: listen EADDRINUS的錯(cuò)誤,而Cluster模塊能夠讓多個(gè)子進(jìn)程監(jiān)聽(tīng)同一個(gè)端口的原因是master進(jìn)程內(nèi)部啟動(dòng)了一個(gè) TCP 服務(wù)器,而真正監(jiān)聽(tīng)端口的只有這個(gè)服務(wù)器,當(dāng)來(lái)自前端的請(qǐng)求觸發(fā)服務(wù)器的connection事件后,master會(huì)將對(duì)應(yīng)的socket句柄發(fā)送給子進(jìn)程。

到此這篇關(guān)于深入了解 Node的多進(jìn)程服務(wù)實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Node 多進(jìn)程服務(wù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 配置Node.js環(huán)境變量詳細(xì)圖文教程

    配置Node.js環(huán)境變量詳細(xì)圖文教程

    這篇文章主要給大家介紹了關(guān)于配置Node.js環(huán)境變量詳細(xì)圖文教程的相關(guān)資料,在Node.js中設(shè)置環(huán)境變量非常簡(jiǎn)單,文中通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下
    2023-11-11
  • 初識(shí)Node.js

    初識(shí)Node.js

    本文主要是簡(jiǎn)單講訴了Node.js的介紹,安裝,希望對(duì)剛剛接觸Node.js的同學(xué)能有所幫助,有什么問(wèn)題可以給我留言,一起學(xué)習(xí)進(jìn)步
    2014-09-09
  • nodejs處理圖片的中間件node-images詳解

    nodejs處理圖片的中間件node-images詳解

    這篇文章主要介紹了nodejs處理圖片的中間件node-images詳解,非常具有實(shí)用價(jià)值,需要的朋友可以參考下
    2017-05-05
  • Node.js中同步和異步編程的區(qū)別及使用方法

    Node.js中同步和異步編程的區(qū)別及使用方法

    在Node.js中,同步和異步編程是兩種不同的處理方式。同步方式會(huì)阻塞程序的執(zhí)行,而異步方式則不會(huì)。通過(guò)掌握它們的區(qū)別和使用方法,可以更好地實(shí)現(xiàn)程序的性能優(yōu)化和功能擴(kuò)展。同時(shí),需要注意異步編程中的回調(diào)地獄問(wèn)題,使用Promise可以更好地處理異步編程
    2023-05-05
  • Nodejs極簡(jiǎn)入門(mén)教程(三):進(jìn)程

    Nodejs極簡(jiǎn)入門(mén)教程(三):進(jìn)程

    這篇文章主要介紹了Nodejs極簡(jiǎn)入門(mén)教程(三):進(jìn)程,本文講解了Node 進(jìn)程間通信、cluster 模塊等內(nèi)容,需要的朋友可以參考下
    2014-10-10
  • Node.js pipe實(shí)現(xiàn)源碼解析

    Node.js pipe實(shí)現(xiàn)源碼解析

    這篇文章主要介紹了Node.js pipe實(shí)現(xiàn)源碼解析,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-08-08
  • 輕松創(chuàng)建nodejs服務(wù)器(9):實(shí)現(xiàn)非阻塞操作

    輕松創(chuàng)建nodejs服務(wù)器(9):實(shí)現(xiàn)非阻塞操作

    這篇文章主要介紹了輕松創(chuàng)建nodejs服務(wù)器(9):實(shí)現(xiàn)非阻塞操作,本系列文章會(huì)教你一步一步創(chuàng)建一個(gè)完整的服務(wù)器,要的朋友可以參考下
    2014-12-12
  • 詳解一些適用于Node.js的命名約定

    詳解一些適用于Node.js的命名約定

    這篇文章主要介紹了詳解一些適用于Node.js的命名約定,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-12-12
  • node.js實(shí)現(xiàn)逐行讀取文件內(nèi)容的代碼

    node.js實(shí)現(xiàn)逐行讀取文件內(nèi)容的代碼

    這篇文章主要介紹了node.js實(shí)現(xiàn)逐行讀取文件內(nèi)容的代碼,本文還介紹了一個(gè)node.js的按行讀取內(nèi)容開(kāi)源項(xiàng)目,需要的朋友可以參考下
    2014-06-06
  • nodejs發(fā)送http請(qǐng)求時(shí)遇到404長(zhǎng)時(shí)間未響應(yīng)的解決方法

    nodejs發(fā)送http請(qǐng)求時(shí)遇到404長(zhǎng)時(shí)間未響應(yīng)的解決方法

    這篇文章主要為大家詳細(xì)介紹了nodejs發(fā)送http請(qǐng)求時(shí)遇到404長(zhǎng)時(shí)間未響應(yīng)的解決方法
    2017-12-12

最新評(píng)論