【原創(chuàng)】學(xué)習(xí)NodeJs多進程(一)

NodeJs基于事件驅(qū)動的服務(wù)模型芝囤,采用單線程避免了不必要的內(nèi)存開銷和上下文切換的開銷似炎,但是同時也帶來了一些問題,比如單線程不能充分利用多核CPU資源悯姊,并且進程出現(xiàn)未捕獲的異常會導(dǎo)致進程直接退出羡藐。NodeJs提供了子進程和集群模塊,幫助我們使用NodeJs多進程來充分利用CPU資源和提高應(yīng)用的健壯性悯许。

相關(guān)文章

目錄

  • 創(chuàng)建子進程
  • 進程間通信
  • 端口共同監(jiān)聽
  • 多進程需要考慮的問題

創(chuàng)建子進程

NodeJs使用child_process模塊來創(chuàng)建子進程仆嗦。基礎(chǔ)的兩個方法為child_process.spawn() 先壕、child_process.spawnSync()瘩扼,前者異步地創(chuàng)建子進程谆甜,且不阻塞 Node.js 事件循環(huán);后者則以同步的方式提供等效功能集绰,但會阻止事件循環(huán)直到衍生的進程退出或終止规辱。由于child_process.spawnSync()不常用,此處不做介紹栽燕。

child_process模塊基于child_process.spawn() 方法實現(xiàn)了其他幾個創(chuàng)建子進程方法罕袋,簡要介紹如下:

  • child_process.spawn(command[, args][, options]):根據(jù)命令創(chuàng)建子進程,返回子進程對象碍岔,可以在子進程對象上注冊事件
  • child_process.exec(command[, options][, callback]):創(chuàng)建一個shell環(huán)境進程并在該shell中運行命令浴讯,UNIX上是 '/bin/sh',windows上是'cmd.exe'蔼啦,可通過options.shell指定程序
  • child_process.execFile(file[, args][, options][, callback]):類似于child_process.exec()榆纽,不創(chuàng)建shell直接根據(jù)命令創(chuàng)建子進程
  • child_process.fork():創(chuàng)建一個新的 Node.js 進程,并通過建立 IPC 通信通道來調(diào)用指定的模塊捏肢,該通道允許在父進程與子進程之間發(fā)送消息奈籽。

基礎(chǔ)使用方式如下:

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

const child= spawn('node', ['--version'])
child.stdout.on('data', (data) => {
  console.log(`spawn stdout: ${data}`)
})

exec('node --version', (error, stdout, stderr) => {
  if (error) {
    throw error
  }
  console.log(`exec stdout: ${stdout}`)
})

execFile('node', ['--version'], (error, stdout, stderr) => {
  if (error) {
    throw error
  }
  console.log(`execFile stdout: ${stdout}`)
})

執(zhí)行結(jié)果:

spawn stdout: v10.15.3
execFile stdout: v10.15.3
exec stdout: v10.15.3

由于child_process.exec()child_process.execFile()是由child_process.spawn()實現(xiàn)的,它們執(zhí)行返回的子進程對象和child_process.spawn()一樣可以獲取子進程的stdout猛计、stderr唠摹,只不過以回調(diào)方法的方式寫法簡單一些。

child_process.fork()只能創(chuàng)建一個node的子進程奉瘤,只要指定模塊即可。相比于其他方式創(chuàng)建子進程煮甥,該方式可以和子進程相互通信盗温,通信方式也很簡單,監(jiān)聽message事件接收消息成肘,使用send()方法發(fā)送消息卖局,使用方式如下:

// parent.js
const { fork } = require('child_process')
const path = require('path')
const child = fork(path.resolve(__dirname, './child.js'))
child.on('message', function (msg) {
  console.log('Message from child: ', msg)
})
child.send('hello world')

// child.js
process.on('message', function (msg) {
  console.log('Message from parent:', msg)
  process.send(msg)
})

執(zhí)行parent.js,結(jié)果如下:

Message from parent: hello world
Message from child:  hello world

由以上四種方式創(chuàng)建子進程双霍,都能獲取到子進程對象ChildProcess
的實例砚偶,它提供了closedisconnect洒闸、error染坯、exitmessage等事件與子進程交互丘逸。

更多關(guān)于子進程的api单鹿,請閱讀官方文檔:http://nodejs.cn/api/child_process.html

進程間通信

由上節(jié)child_process.fork()的示例可以看到肄渗,進程間通過監(jiān)聽message事件接收消息霉旗,使用send()方法發(fā)送消息划址,它們是基于IPC實現(xiàn)的呢岗。

IPC的全稱是Inter-Process Communication,即進程間通信儒喊。Node中實現(xiàn)IPC通道的是管道(pipe)技術(shù)镣奋,具體細節(jié)實現(xiàn)依賴系統(tǒng)底層。借用《深入淺出Node.js》中的圖來表示創(chuàng)建IPC管道的過程怀愧,如下:

創(chuàng)建IPC管道的示意圖

當(dāng)父進程調(diào)用child_process.fork()創(chuàng)建子進程的時候侨颈,先創(chuàng)建IPC管道并監(jiān)聽它,創(chuàng)建成功后再創(chuàng)建子進程掸驱,并把IPC管道的文件描述符通過環(huán)境變量傳遞給子進程肛搬,子進程啟動后根據(jù)IPC管道的文件描述符去連接IPC通道,連接成功后毕贼,父子進程就能通過IPC管道通信了温赔。

端口共同監(jiān)聽

常規(guī)情況下,啟動兩個node程序去監(jiān)聽同一個端口時鬼癣,后一個程序會提示端口已占用陶贼,那在多進程服務(wù)中如何只監(jiān)聽一個端口把請求分發(fā)給多個進程處理呢?其實上文用于消息傳遞的send()方法的第二個參數(shù)支持傳遞句柄待秃,來看一個例子:

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

const child1 = fork(path.resolve(__dirname, './child.js'))
const child2 = fork(path.resolve(__dirname, './child.js'))

const server = require('net').createServer()

server.on('connection', (socket) => {
  socket.end('handle by parent')
})

server.listen(3000, () => {
  child1.send('server', server)
  child2.send('server', server)
})

// child.js
process.on('message', function (msg, server) {
  if (msg === 'server') {
    server.on('connection', (socket) => {
      socket.end(`handle by child ${process.pid}`)
    })
  }
})

運行parent.js后拜秧,多次訪問http://127.0.0.1:3000,效果如下:

多進程監(jiān)聽同一端口執(zhí)行結(jié)果

可以看到多個進程監(jiān)聽了同一個端口3000章郁,并且多次訪問之后枉氮,真正處理請求的進程是不確定的∨看到這里聊替,想必會有以下疑問。

主進程將server對象傳到子進程了嗎?

其實這里傳遞的server對象的句柄培廓,子進程接受到server對象的句柄惹悄,獲得父進程server對象的信息,再重新創(chuàng)建server對象肩钠。對于調(diào)用者而言泣港,就像把server對象直接傳遞到了子進程,實際上send()只有消息傳遞价匠。

為什么多進程監(jiān)聽同一端口不報錯?

在TCP端socket套接字監(jiān)聽端口有一個文件描述符当纱,單獨啟動多個進程時文件描述符不同,導(dǎo)致監(jiān)聽相同端口會報錯霞怀。NodeJs底層對每個端口監(jiān)聽都設(shè)置了標(biāo)識惫东,在父進程和子進程傳遞server對象的過程中,將標(biāo)識傳給了對方,因此通過標(biāo)識它們監(jiān)聽端口用的是同一個文件描述符廉沮。在網(wǎng)絡(luò)請求向服務(wù)器發(fā)送時颓遏,這些進程通過搶占為請求服務(wù)。

send()方法除了server對象還支持發(fā)送哪些對象?

要發(fā)送類似的對象滞时,需要有完整的發(fā)送與還原對象的過程叁幢。根據(jù)官方文檔描述,支持的對象如下:

  • net.Socket TCP套接字
  • net.Server TCP服務(wù)器
  • dgram.Socket UDP套接字

多進程需要考慮的問題

  • 多進程開發(fā)
    根據(jù)上文介紹的子進程創(chuàng)建和進程間通信坪稽,如果讓開發(fā)者手動來處理父子進程是比較麻煩的事情曼玩。幸好NodeJs官方提供了cluster模塊,讓多進程的使用變得很容易窒百。
  • 負載均衡
    多個進程間需要有一個策略來保證資源的合理分配黍判。Node默認提供的機制是采用操作系統(tǒng)的搶占式策略,但也需要根據(jù)實際系統(tǒng)的資源使用情況來考慮篙梢。
  • 進程管理
    為了程序的健壯性以及充分利用CPU資源顷帖,我們引入多進程,那么多進程的管理也是一個問題渤滞,比如某個子進程異常退出需要自動創(chuàng)建一個新的子進程贬墩、讓所有的子進程去搶占端口請求會造成性能浪費等。目前開源好用的進程管理工具有pandora妄呕、pm2可以幫助我們解決一些問題陶舞。
  • 狀態(tài)共享
    通常在多個應(yīng)用間需要有一些共享數(shù)據(jù),比如IM系統(tǒng)中記錄當(dāng)前在線的用戶绪励。常見的做法是通過第三方數(shù)據(jù)存儲來實現(xiàn)肿孵,比如redis

關(guān)于以上問題將在后面的文章中繼續(xù)學(xué)習(xí)探索疏魏。

總結(jié)

本文簡要介紹了子進程創(chuàng)建和進程間通信的基礎(chǔ)內(nèi)容颁井,在后面的文章中將深入學(xué)習(xí)多進程的管理。

本文參考資源如下

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蠢护,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子养涮,更是在濱河造成了極大的恐慌葵硕,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件贯吓,死亡現(xiàn)場離奇詭異懈凹,居然都是意外死亡,警方通過查閱死者的電腦和手機悄谐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門介评,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事们陆『撸” “怎么了?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵杂腰,是天一觀的道長。 經(jīng)常有香客問我椅文,道長喂很,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任漓帅,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘腿箩。我一直安慰自己豪直,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布懈玻。 她就那樣靜靜地躺著涂乌,像睡著了一般湾盒。 火紅的嫁衣襯著肌膚如雪场刑。 梳的紋絲不亂的頭發(fā)上蚪战,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天捏萍,我揣著相機與錄音,去河邊找鬼逗噩。 笑死掉丽,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的异雁。 我是一名探鬼主播捶障,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼纲刀!你這毒婦竟也來了项炼?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤示绊,失蹤者是張志新(化名)和其女友劉穎芥挣,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體耻台,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年空另,在試婚紗的時候發(fā)現(xiàn)自己被綠了盆耽。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖摄杂,靈堂內(nèi)的尸體忽然破棺而出坝咐,到底是詐尸還是另有隱情,我是刑警寧澤析恢,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布墨坚,位于F島的核電站,受9級特大地震影響映挂,放射性物質(zhì)發(fā)生泄漏泽篮。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一柑船、第九天 我趴在偏房一處隱蔽的房頂上張望帽撑。 院中可真熱鬧,春花似錦鞍时、人聲如沸亏拉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽及塘。三九已至,卻和暖如春锐极,著一層夾襖步出監(jiān)牢的瞬間笙僚,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工溪烤, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留味咳,地道東北人。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓檬嘀,卻偏偏與公主長得像槽驶,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子鸳兽,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,611評論 2 353