深入理解NodeJS 多進(jìn)程和集群
進(jìn)程和線程
“進(jìn)程” 是計(jì)算機(jī)系統(tǒng)進(jìn)行資源分配和調(diào)度的基本單位,我們可以理解為計(jì)算機(jī)每開(kāi)啟一個(gè)任務(wù)就會(huì)創(chuàng)建至少一個(gè)進(jìn)程來(lái)處理,有時(shí)會(huì)創(chuàng)建多個(gè),如 Chrome 瀏覽器的選項(xiàng)卡,其目的是為了防止一個(gè)進(jìn)程掛掉而應(yīng)用停止工作,而 “線程” 是程序執(zhí)行流的最小單元,NodeJS 默認(rèn)是單進(jìn)程、單線程的,我們將這個(gè)進(jìn)程稱為主進(jìn)程,也可以通過(guò) child_process 模塊創(chuàng)建子進(jìn)程實(shí)現(xiàn)多進(jìn)程,我們稱這些子進(jìn)程為 “工作進(jìn)程”,并且歸主進(jìn)程管理,進(jìn)程之間默認(rèn)是不能通信的,且所有子進(jìn)程執(zhí)行任務(wù)都是異步的。
spawn 實(shí)現(xiàn)多進(jìn)程
1、spawn 創(chuàng)建子進(jìn)程
在 NodeJS 中執(zhí)行一個(gè) JS 文件,如果想在這個(gè)文件中再同時(shí)(異步)執(zhí)行另一個(gè) JS 文件,可以使用 child_process 模塊中的 spawn 來(lái)實(shí)現(xiàn),spawn 可以幫助我們創(chuàng)建一個(gè)子進(jìn)程,用法如下。
// 文件:process.js
const { spawn } = require("child_process");
const path = require("path");
// 創(chuàng)建子進(jìn)程
let child = spawn("node", ["sub_process.js", "--port", "3000"], {
cwd: path.join(__dirname, "test") // 指定子進(jìn)程的當(dāng)前工作目錄
});
// 出現(xiàn)錯(cuò)誤觸發(fā)
child.on("error", err => console.log(err));
// 子進(jìn)程退出觸發(fā)
child.on("exit", () => console.log("exit"));
// 子進(jìn)程關(guān)閉觸發(fā)
child.on("close", () => console.log("close"));
// exit
// close
spawn 方法可以幫助我們創(chuàng)建一個(gè)子進(jìn)程,這個(gè)子進(jìn)程就是方法的返回值,spawn 接收以下幾個(gè)參數(shù):
- command:要運(yùn)行的命令;
- args:類型為數(shù)組,數(shù)組內(nèi)第一項(xiàng)為文件名,后面項(xiàng)依次為執(zhí)行文件的命令參數(shù)和值;
- options:選項(xiàng),類型為對(duì)象,用于指定子進(jìn)程的當(dāng)前工作目錄和主進(jìn)程、子進(jìn)程的通信規(guī)則等,具體可查看 官方文檔。
error 事件在子進(jìn)程出錯(cuò)時(shí)觸發(fā),exit 事件在子進(jìn)程退出時(shí)觸發(fā),close 事件在子進(jìn)程關(guān)閉后觸發(fā),在子進(jìn)程任務(wù)結(jié)束后 exit 一定會(huì)觸發(fā),close 不一定觸發(fā)。
// 文件:~test/sub_process.js // 打印子進(jìn)程執(zhí)行 sub_process.js 文件的參數(shù) console.log(process.argv);
通過(guò)上面代碼打印了子進(jìn)程執(zhí)行時(shí)的參數(shù),但是我們發(fā)現(xiàn)主進(jìn)程窗口并沒(méi)有打印,我們希望的是子進(jìn)程的信息可以反饋給主進(jìn)程,要實(shí)現(xiàn)通信需要在創(chuàng)建子進(jìn)程時(shí)在第三個(gè)參數(shù) options 中配置 stdio 屬性定義。
2、spawn 定義輸入、輸出
// 文件:process.js
const { spawn } = require("child_process");
const path = require("path");
// 創(chuàng)建子進(jìn)程
let child = spawn("node", ["sub_process.js", "--port", "3000"], {
cwd: path.join(__dirname, "test") // 指定子進(jìn)程的當(dāng)前工作目錄
// stdin: [process.stdin, process.stdout, process.stderr]
stdio: [0, 1, 2] // 配置標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出、錯(cuò)誤輸出
});
// C:\Program Files\nodejs\node.exe,g:\process\test\sub_process.js,--port,3000
// 文件:~test/sub_process.js // 使用主進(jìn)程的標(biāo)準(zhǔn)輸出,輸出 sub_process.js 文件執(zhí)行的參數(shù) process.stdout.write(process.argv.toString());
通過(guò)上面配置 options 的 stdio 值為數(shù)組,上面的兩種寫(xiě)法作用相同,都表示子進(jìn)程和主進(jìn)程共用了主進(jìn)程的標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出、和錯(cuò)誤輸出,實(shí)際上并沒(méi)有實(shí)現(xiàn)主進(jìn)程與子進(jìn)程的通信,其中 0 和 stdin 代表標(biāo)準(zhǔn)輸入,1 和 stdout 代表標(biāo)準(zhǔn)輸出,2 和 stderr 代表錯(cuò)誤輸出。
上面這樣的方式只要子進(jìn)程執(zhí)行 sub_process.js 就會(huì)在窗口輸出,如果我們希望是否輸出在主進(jìn)程里面控制,即實(shí)現(xiàn)子進(jìn)程與主進(jìn)程的通信,看下面用法。
// 文件:process.js
const { spawn } = require("child_process");
const path = require("path");
// 創(chuàng)建子進(jìn)程
let child = spawn("node", ["sub_process.js"], {
cwd: path.join(__dirname, "test"),
stdio: ["pipe"]
});
child.stdout.on("data", data => console.log(data.toString()));
// hello world
// 文件:~test/sub_process.js
// 子進(jìn)程執(zhí)行 sub_process.js
process.stdout.write("hello world");
上面將 stdio 內(nèi)數(shù)組的值配置為 pipe(默認(rèn)不寫(xiě)就是 pipe),則通過(guò)流的方式實(shí)現(xiàn)主進(jìn)程和子進(jìn)程的通信,通過(guò)子進(jìn)程的標(biāo)準(zhǔn)輸出(可寫(xiě)流)寫(xiě)入,在主進(jìn)程通過(guò)子進(jìn)程的標(biāo)準(zhǔn)輸出通過(guò) data 事件讀取的流在輸出到窗口(這種寫(xiě)法很少用),上面都只在主進(jìn)程中開(kāi)啟了一個(gè)子進(jìn)程,下面舉一個(gè)開(kāi)啟多個(gè)進(jìn)程的例子。
例子的場(chǎng)景是主進(jìn)程開(kāi)啟兩個(gè)子進(jìn)程,先運(yùn)行子進(jìn)程 1 傳遞一些參數(shù),子進(jìn)程 1 將參數(shù)取出返還給主進(jìn)程,主進(jìn)程再把參數(shù)傳遞給子進(jìn)程 2,通過(guò)子進(jìn)程 2 將參數(shù)寫(xiě)入到文件 param.txt 中,這個(gè)過(guò)程不代表真實(shí)應(yīng)用場(chǎng)景,主要目的是體會(huì)主進(jìn)程和子進(jìn)程的通信過(guò)程。
// 文件:process.js
const { spawn } = require("child_process");
const path = require("path");
// 創(chuàng)建子進(jìn)程
let child1 = spawn("node", ["sub_process_1.js", "--port", "3000"], {
cwd: path.join(__dirname, "test"),
});
let child2 = spawn("node", ["sub_process_2.js"], {
cwd: path.join(__dirname, "test"),
});
// 讀取子進(jìn)程 1 寫(xiě)入的內(nèi)容,寫(xiě)入子進(jìn)程 2
child1.stdout.on("data", data => child2.stdout.write(data.toString));
// 文件:~test/sub_process_1.js // 獲取 --port 和 3000 process.argv.slice(2).forEach(item => process.stdout.write(item));
// 文件:~test/sub_process_2.js
const fs = require("fs");
// 讀取主進(jìn)程傳遞的參數(shù)并寫(xiě)入文件
process.stdout.on("data", data => {
fs.writeFile("param.txt", data, () => {
process.exit();
});
});
有一點(diǎn)需要注意,在子進(jìn)程 2 寫(xiě)入文件的時(shí)候,由于主進(jìn)程不知道子進(jìn)程 2 什么時(shí)候?qū)懲?,所以主進(jìn)程會(huì)卡住,需要子進(jìn)程在寫(xiě)入完成后調(diào)用 process.exit 方法退出子進(jìn)程,子進(jìn)程退出并關(guān)閉后,主進(jìn)程會(huì)隨之關(guān)閉。
在我們給 options 配置 stdio 時(shí),數(shù)組內(nèi)其實(shí)可以對(duì)標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出和錯(cuò)誤輸出分開(kāi)配置,默認(rèn)數(shù)組內(nèi)為 pipe 時(shí)代表三者都為 pipe,分別配置看下面案例。
// 文件:process.js
const { spawn } = require("spawn");
const path = require("path");
// 創(chuàng)建子進(jìn)程
let child = spawn("node", ["sub_process.js"], {
cwd: path.join(__dirname, "test"),
stdio: [0, "pipe", 2]
});
// world
// 文件:~test/sub_process.js
console.log("hello");
console.error("world");
上面代碼中對(duì) stderr 實(shí)現(xiàn)了默認(rèn)打印而不通信,對(duì)標(biāo)準(zhǔn)輸入實(shí)現(xiàn)了通信,還有一種情況,如果希望子進(jìn)程只是默默的執(zhí)行任務(wù),而在主進(jìn)程命令窗口什么類型的輸出都禁止,可以在數(shù)組中對(duì)應(yīng)位置給定值 ignore,將上面案例修改如下。
// 文件:process.js
const { spawn } = require("spawn");
const path = require("path");
// 創(chuàng)建子進(jìn)程
let child = spawn("node", ["sub_process.js"], {
cwd: path.join(__dirname, "test"),
stdio: [0, "pipe", "ignore"]
});
// 文件:~test/sub_process.js
console.log("hello");
console.error("world");
這次我們發(fā)現(xiàn)無(wú)論標(biāo)準(zhǔn)輸出和錯(cuò)誤輸出都沒(méi)有生效,上面這些方式其實(shí)是不太方便的,因?yàn)檩敵鲇?stdout 和 stderr,在寫(xiě)法上沒(méi)辦法統(tǒng)一,可以通過(guò)下面的方式來(lái)統(tǒng)一。
3、標(biāo)準(zhǔn)進(jìn)程通信
// 文件:process.js
const { spawn } = require("spawn");
const path = require("path");
// 創(chuàng)建子進(jìn)程
let child = spawn("node", ["sub_process.js"], {
cwd: path.join(__dirname, "test"),
stdio: [0, "pipe", "ignore", "ipc"]
});
child.on("message", data => {
console.log(data);
// 回復(fù)消息給子進(jìn)程
child.send("world");
// 殺死子進(jìn)程
// process.kill(child.pid);
});
// hello
// 文件:~test/sub_process.js
// 給主進(jìn)程發(fā)送消息
process.send("hello");
// 接收主進(jìn)程回復(fù)的消息
process.on("message", data => {
console.log(data);
// 退出子進(jìn)程
process.exit();
});
// world
這種方式被稱為標(biāo)準(zhǔn)進(jìn)程通信,通過(guò)給 options 的 stdio 數(shù)組配置 ipc,只要數(shù)組中存在 ipc 即可,一般放在數(shù)組開(kāi)頭或結(jié)尾,配置 ipc 后子進(jìn)程通過(guò)調(diào)用自己的 send 方法發(fā)送消息給主進(jìn)程,主進(jìn)程中用子進(jìn)程的 message 事件進(jìn)行接收,也可以在主進(jìn)程中接收消息的 message 事件的回調(diào)當(dāng)中,通過(guò)子進(jìn)程的 send 回復(fù)消息,并在子進(jìn)程中用 message 事件進(jìn)行接收,這樣的編程方式比較統(tǒng)一,更貼近于開(kāi)發(fā)者的意愿。
4、退出和殺死子進(jìn)程
上面代碼中子進(jìn)程在接收到主進(jìn)程的消息時(shí)直接退出,也可以在子進(jìn)程發(fā)送給消息給主進(jìn)程時(shí),主進(jìn)程接收到消息直接殺死子進(jìn)程,代碼如下。
// 文件:process.js
const { spawn } = require("spawn");
const path = require("path");
// 創(chuàng)建子進(jìn)程
let child = spawn("node", ["sub_process.js"], {
cwd: path.join(__dirname, "test"),
stdio: [0, "pipe", "ignore", "ipc"]
});
child.on("message", data => {
console.log(data);
// 殺死子進(jìn)程
process.kill(child.pid);
});
// hello world
// 文件:~test/sub_process.js
// 給主進(jìn)程發(fā)送消息
process.send("hello");
從上面代碼我們可以看出,殺死子進(jìn)程的方法為 process.kill,由于一個(gè)主進(jìn)程可能有多個(gè)子進(jìn)程,所以指定要?dú)⑺赖淖舆M(jìn)程需要傳入子進(jìn)程的 pid 屬性作為 process.kill 的參數(shù)。
{% note warning %}
注意:退出子進(jìn)程 process.exit 方法是在子進(jìn)程中操作的,此時(shí) process 代表子進(jìn)程,殺死子進(jìn)程 process.kill 是在主進(jìn)程中操作的,此時(shí) process 代表主進(jìn)程。
{% endnote %}
5、獨(dú)立子進(jìn)程
我們前面說(shuō)過(guò),child_process 模塊創(chuàng)建的子進(jìn)程是被主進(jìn)程統(tǒng)一管理的,如果主進(jìn)程掛了,所有的子進(jìn)程也會(huì)受到影響一起掛掉,但其實(shí)使用多進(jìn)程一方面為了提高處理任務(wù)的效率,另一方面也是為了當(dāng)一個(gè)進(jìn)程掛掉時(shí)還有其他進(jìn)程可以繼續(xù)工作,不至于整個(gè)應(yīng)用掛掉,這樣的例子非常多,比如 Chrome 瀏覽器的選項(xiàng)卡,比如 VSCode 編輯器運(yùn)行時(shí)都會(huì)同時(shí)開(kāi)啟多個(gè)進(jìn)程同時(shí)處理任務(wù),其實(shí)在 spawn 創(chuàng)建子進(jìn)程時(shí),也可以實(shí)現(xiàn)子進(jìn)程的獨(dú)立,即子進(jìn)程不再受主進(jìn)程的控制和影響。
// 文件:process.js
const { spawn } = require("spawn");
const path = require("path");
// 創(chuàng)建子進(jìn)程
let child = spawn("node", ["sub_process.js"], {
cwd: path.join(__dirname, "test"),
stdio: "ignore",
detached: true
});
// 與主進(jìn)程斷絕關(guān)系
child.unref();
// 文件:~test/sub_process.js
const fs = require("fs");
setInterval(() => {
fs.appendFileSync("test.txt", "hello");
});
要想創(chuàng)建的子進(jìn)程獨(dú)立,需要在創(chuàng)建子進(jìn)程時(shí)配置 detached 參數(shù)為 true,表示該子進(jìn)程不受控制,還需調(diào)用子進(jìn)程的 unref 方法與主進(jìn)程斷絕關(guān)系,但是僅僅這樣子進(jìn)程可能還是會(huì)受主進(jìn)程的影響,要想子進(jìn)程完全獨(dú)立需要保證子進(jìn)程一定不能和主進(jìn)程共用標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出和錯(cuò)誤輸出,也就是 stdio 必須設(shè)置為 ignore,這也就代表著獨(dú)立的子進(jìn)程是不能和主進(jìn)程進(jìn)行標(biāo)準(zhǔn)進(jìn)程通信,即不能設(shè)置 ipc。
fork 實(shí)現(xiàn)多進(jìn)程
1、fork 的使用
fork 也是 child_process 模塊的一個(gè)方法,與 spawn 類似,是在 spawn 的基礎(chǔ)上又做了一層封裝,我們看一個(gè) fork 使用的例子。
// 文件:process.js
const fork = require("child_process");
const path = require("path");
// 創(chuàng)建子進(jìn)程
let child = fork("sub_process.js", ["--port", "3000"], {
cwd: path.join(__dirname, "test"),
silent: true
});
child.send("hello world");
// 文件:~test/sub_process.js
// 接收主進(jìn)程發(fā)來(lái)的消息
process.on("message", data => console.log(data));
fork 的用法與 spawn 相比有所改變,第一個(gè)參數(shù)是子進(jìn)程執(zhí)行文件的名稱,第二個(gè)參數(shù)為數(shù)組,存儲(chǔ)執(zhí)行時(shí)的參數(shù)和值,第三個(gè)參數(shù)為 options,其中使用 slilent 屬性替代了 spawn 的 stdio,當(dāng) silent 為 true 時(shí),此時(shí)主進(jìn)程與子進(jìn)程的所有非標(biāo)準(zhǔn)通信的操作都不會(huì)生效,包括標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出和錯(cuò)誤輸出,當(dāng)設(shè)為 false 時(shí)可正常輸出,返回值依然為一個(gè)子進(jìn)程。
fork 創(chuàng)建的子進(jìn)程可以直接通過(guò) send 方法和監(jiān)聽(tīng) message 事件與主進(jìn)程進(jìn)行通信。
2、fork 的原理
其實(shí) fork 的原理非常簡(jiǎn)單,只是在子進(jìn)程模塊 child_process 上掛了一個(gè) fork 方法,而在該方法內(nèi)調(diào)用 spawn 并將 spawn 返回的子進(jìn)程作為返回值返回,下面進(jìn)行簡(jiǎn)易實(shí)現(xiàn)。
// 文件:fork.js
const childProcess = require("child_process");
const path = require("path");
// 封裝原理
childProcess.fork = function (modulePath, args, options) {
let stdio = options.silent ? ["ignore", "ignore", "ignore", "ipc"] : [0, 1, 2, "ipc"];
return childProcess.spawn("node", [modulePath, ...args], {
...options,
stdio
});
}
// 創(chuàng)建子進(jìn)程
let child = fork("sub_process.js", ["--port", "3000"], {
cwd: path.join(__dirname, "test"),
silent: false
});
// 向子進(jìn)程發(fā)送消息
child.send("hello world");
// 文件:~test/sub_process.js
// 接收主進(jìn)程發(fā)來(lái)的消息
process.on("message", data => console.log(data));
// hello world
spawn 中的有一些 fork 沒(méi)有傳的參數(shù)(如使用 node 執(zhí)行文件),都在內(nèi)部調(diào)用 spawn 時(shí)傳遞默認(rèn)值或?qū)⒛J(rèn)參數(shù)與 fork 傳入的參數(shù)進(jìn)行整合,著重處理了 spawn 沒(méi)有的參數(shù) silent,其實(shí)就是處理成了 spawn 的 stdio 參數(shù)兩種極端的情況(默認(rèn)使用 ipc 通信),封裝 fork 就是讓我們能更方便的創(chuàng)建子進(jìn)程,可以更少的傳參。
execFile 和 exec 實(shí)現(xiàn)多進(jìn)程
execFile 和 exec 是 child_process 模塊的兩個(gè)方法,execFile 是基于 spawn 封裝的,而 exec 是基于 execFile 封裝的,這兩個(gè)方法用法大同小異,execFile 可以直接創(chuàng)建子進(jìn)程進(jìn)行文件操作,而 exec 可以直接開(kāi)啟子進(jìn)程執(zhí)行命令,常見(jiàn)的應(yīng)用場(chǎng)景如 http-server 以及 weboack-dev-server 等命令行工具在啟動(dòng)本地服務(wù)時(shí)自動(dòng)打開(kāi)瀏覽器。
// execFile 和 exec
const { execFile, exec } = require("child_process");
let execFileChild = execFile("node", ["--version"], (err, stdout, stderr) => {
if (error) throw error;
console.log(stdout);
console.log(stderr);
});
let execChild = exec("node --version", (err, stdout, stderr) => {
if (err) throw err;
console.log(stdout);
console.log(stderr);
});
exec 與 execFile 的區(qū)別在于傳參,execFile 第一個(gè)參數(shù)為文件的可執(zhí)行路徑或命令,第二個(gè)參數(shù)為命令的參數(shù)集合(數(shù)組),第三個(gè)參數(shù)為 options,最后一個(gè)參數(shù)為回調(diào)函數(shù),回調(diào)函數(shù)的形參為錯(cuò)誤、標(biāo)準(zhǔn)輸出和錯(cuò)誤輸出。
exec 在傳參上將 execFile 的前兩個(gè)參數(shù)進(jìn)行了整合,也就是命令與命令參數(shù)拼接成字符串作為第一參數(shù),后面的參數(shù)都與 execFile 相同。
cluster 集群
開(kāi)啟進(jìn)程需要消耗內(nèi)存,所以開(kāi)啟進(jìn)程的數(shù)量要適合,合理運(yùn)用多進(jìn)程可以大大提高效率,如 Webpack 對(duì)資源進(jìn)行打包,就開(kāi)啟了多個(gè)進(jìn)程同時(shí)進(jìn)行,大大提高了打包速度,集群也是多進(jìn)程重要的應(yīng)用之一,用多個(gè)進(jìn)程同時(shí)監(jiān)聽(tīng)同一個(gè)服務(wù),一般開(kāi)啟進(jìn)程的數(shù)量跟 CPU 核數(shù)相同為好,此時(shí)多個(gè)進(jìn)程監(jiān)聽(tīng)的服務(wù)會(huì)根據(jù)請(qǐng)求壓力分流處理,也可以通過(guò)設(shè)置每個(gè)子進(jìn)程處理請(qǐng)求的數(shù)量來(lái)實(shí)現(xiàn) “負(fù)載均衡”。
1、使用 ipc 實(shí)現(xiàn)集群
ipc 標(biāo)準(zhǔn)進(jìn)程通信使用 send 方法發(fā)送消息時(shí)第二個(gè)參數(shù)支持傳入一個(gè)服務(wù),必須是 http 服務(wù)或者 tcp 服務(wù),子進(jìn)程通過(guò) message 事件進(jìn)行接收,回調(diào)的參數(shù)分別對(duì)應(yīng)發(fā)送的參數(shù),即第一個(gè)參數(shù)為消息,第二個(gè)參數(shù)為服務(wù),我們就可以在子進(jìn)程創(chuàng)建服務(wù)并對(duì)主進(jìn)程的服務(wù)進(jìn)行監(jiān)聽(tīng)和操作(listen 除了可以監(jiān)聽(tīng)端口號(hào)也可以監(jiān)聽(tīng)服務(wù)),便實(shí)現(xiàn)了集群,代碼如下。
// 文件:server.js
const os = require("os"); // os 模塊用于獲取系統(tǒng)信息
const http = require("http");
const path = require("path");
const { fork } = rquire("child_process");
// 創(chuàng)建服務(wù)
const server = createServer((res, req) => {
res.end("hello");
}).listen(3000);
// 根據(jù) CPU 個(gè)數(shù)創(chuàng)建子進(jìn)程
os.cpus().forEach(() => {
fork("child_server.js", {
cwd: path.join(__dirname);
}).send("server", server);
});
// 文件:child_server.js
const http = require("http");
// 接收來(lái)自主進(jìn)程發(fā)來(lái)的服務(wù)
process.on("message", (data, server) => {
http.createServer((req, res) => {
res.end(`child${process.pid}`);
}).listen(server); // 子進(jìn)程共用主進(jìn)程的服務(wù)
});
上面代碼中由主進(jìn)程處理的請(qǐng)求會(huì)返回 hello,由子進(jìn)程處理的請(qǐng)求會(huì)返回 child 加進(jìn)程的 pid 組成的字符串。
2、使用 cluster 實(shí)現(xiàn)集群
cluster 模塊是 NodeJS 提供的用來(lái)實(shí)現(xiàn)集群的,他將 child_process 創(chuàng)建子進(jìn)程的方法集成進(jìn)去,實(shí)現(xiàn)方式要比使用 ipc 更簡(jiǎn)潔。
// 文件:cluster.js
const cluster = require("cluster");
const http = require("http");
const os = require("os");
// 判斷當(dāng)前執(zhí)行的進(jìn)程是否為主進(jìn)程,為主進(jìn)程則創(chuàng)建子進(jìn)程,否則用子進(jìn)程監(jiān)聽(tīng)服務(wù)
if (cluster.isMaster) {
// 創(chuàng)建子進(jìn)程
os.cpus().forEach(() => cluster.fork());
} else {
// 創(chuàng)建并監(jiān)聽(tīng)服務(wù)
http.createServer((req, res) => {
res.end(`child${process.pid}`);
}).listen(3000);
}
上面代碼既會(huì)執(zhí)行 if 又會(huì)執(zhí)行 else,這看似很奇怪,但其實(shí)不是在同一次執(zhí)行的,主進(jìn)程執(zhí)行時(shí)會(huì)通過(guò) cluster.fork 創(chuàng)建子進(jìn)程,當(dāng)子進(jìn)程被創(chuàng)建會(huì)將該文件再次執(zhí)行,此時(shí)則會(huì)執(zhí)行 else 中對(duì)服務(wù)的監(jiān)聽(tīng),還有另一種用法將主進(jìn)程和子進(jìn)程執(zhí)行的代碼拆分開(kāi),邏輯更清晰,用法如下。
// 文件:cluster.js
const cluster = require("cluster");
const path = require("path");
const os = require("os");
// 設(shè)置子進(jìn)程讀取文件的路徑
cluster.setupMaster({
exec: path.join(__dirname, "cluster-server.js")
});
// 創(chuàng)建子進(jìn)程
os.cpus().forEach(() => cluster.fork());
// 文件:cluster-server.js
const http = require("http");
// 創(chuàng)建并監(jiān)聽(tīng)服務(wù)
http.createServer((req, res) => {
res.end(`child${process.pid}`);
}).listen(3000);
通過(guò) cluster.setupMaster 設(shè)置子進(jìn)程執(zhí)行文件以后,就可以將主進(jìn)程和子進(jìn)程的邏輯拆分開(kāi),在實(shí)際的開(kāi)發(fā)中這樣的方式也是最常用的,耦合度低,可讀性好,更符合開(kāi)發(fā)的原則。
總結(jié)
本篇著重的介紹了 NodeJS 多進(jìn)程的實(shí)現(xiàn)方式以及集群的使用,之所以在開(kāi)頭長(zhǎng)篇大論的介紹 spawn,是因?yàn)槠渌乃懈噙M(jìn)程相關(guān)的方法包括 fork、exec 等,以及模塊 cluster 都是基于 spawn 的封裝,如果對(duì) spawn 足夠了解,其他的也不在話下,希望大家通過(guò)這篇可以在 NodeJS 多進(jìn)程相關(guān)的開(kāi)發(fā)中起到一個(gè) “路標(biāo)” 的作用。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
node.js 利用流實(shí)現(xiàn)讀寫(xiě)同步,邊讀邊寫(xiě)的方法
下面小編就為大家?guī)?lái)一篇node.js 利用流實(shí)現(xiàn)讀寫(xiě)同步,邊讀邊寫(xiě)的方法。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-09-09
NodeJS開(kāi)發(fā)人員常見(jiàn)五個(gè)錯(cuò)誤理解
這篇文章主要介紹了NodeJS開(kāi)發(fā)人員常見(jiàn)五個(gè)錯(cuò)誤理解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-10-10
通過(guò)NodeJS輕松使用GRPC和協(xié)議緩沖區(qū)的方法
本文介紹了GRPC和協(xié)議緩沖區(qū)的基本概念,并展示了如何在NodeJS應(yīng)用程序中使用它們,GRPC是一個(gè)高性能RPC框架,協(xié)議緩沖區(qū)則用于定義服務(wù)和序列化消息,本文給大家介紹如何在NodeJS應(yīng)用程序中使用GRPC和協(xié)議緩沖區(qū),感興趣的朋友一起看看吧2024-10-10
Node.js腳本提取OPML文件信息實(shí)現(xiàn)示例詳解
這篇文章主要為大家介紹了Node.js腳本提取OPML文件信息,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09
Node層模擬實(shí)現(xiàn)multipart表單的文件上傳示例
下面小編就為大家分享一篇Node層模擬實(shí)現(xiàn)multipart表單的文件上傳示例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-01-01
把Node.js程序加入服務(wù)實(shí)現(xiàn)隨機(jī)啟動(dòng)
這篇文章主要介紹了把Node.js程序加入服務(wù)實(shí)現(xiàn)隨機(jī)啟動(dòng),本文使用qckwinsvc實(shí)現(xiàn)這個(gè)需求,講解了qckwinsvc的安裝和使用,需要的朋友可以參考下2015-06-06

