詳解node中創(chuàng)建服務(wù)進(jìn)程
背景
在node工程部署中,常常涉及到三方:本地客戶端、跳板機(jī)和服務(wù)器(集群)。在通過git觸發(fā)gitlab hook腳本后,需要在跳板機(jī)中執(zhí)行相應(yīng)的ssh命令執(zhí)行shell文件啟動(dòng)node服務(wù)器,這需要使用一個(gè)常用的命令setsid,這樣當(dāng)ssh命令執(zhí)行完畢shell退出后,node服務(wù)器仍正常運(yùn)行,此時(shí)node服務(wù)進(jìn)程就是一個(gè)最典型的daemon進(jìn)程(后臺(tái)服務(wù)進(jìn)程)。
那么,在node項(xiàng)目中,如何創(chuàng)建一個(gè)daemon進(jìn)程呢?最簡單的方式,其實(shí)就是采用類似上文中介紹的方式:
require('child_process').exec('setsid node app.js >/dev/null 2>&1 &');
這樣可以通過執(zhí)行shell的方式實(shí)現(xiàn)daemon進(jìn)程。不過本文的重點(diǎn)并不是介紹這種“命令行”的方式實(shí)現(xiàn)daemon進(jìn)程,而且本文會(huì)詳細(xì)講述daemon進(jìn)程的創(chuàng)建原理,且看下文。
目標(biāo)
在當(dāng)前業(yè)務(wù)中,之所以需要?jiǎng)?chuàng)建daemon進(jìn)程就是為了保證中斷創(chuàng)建該進(jìn)程的父進(jìn)程(ctrl+c)或者父進(jìn)程執(zhí)行完畢后并不影響daemon進(jìn)程的執(zhí)行。下文介紹兩種實(shí)現(xiàn)方式,實(shí)現(xiàn)原理細(xì)節(jié)上有些出入。
下文中的所有討論都是在linux環(huán)境下進(jìn)行。
實(shí)現(xiàn)一
在linux系統(tǒng)中,父進(jìn)程創(chuàng)建出子進(jìn)程,此時(shí)父進(jìn)程若退出,此時(shí)子進(jìn)程則變?yōu)楣聝哼M(jìn)程,其ppid變?yōu)?,即成為init進(jìn)程的子進(jìn)程。在node環(huán)境下,如果不針對(duì)子進(jìn)程的stdio做一些特殊處理父進(jìn)程其實(shí)不會(huì)真正退出,而是直到子進(jìn)程執(zhí)行完畢后再退出。之所以出現(xiàn)這種情況是由于node創(chuàng)建子進(jìn)程時(shí)默認(rèn)會(huì)通過pipe方式將子進(jìn)程的輸出導(dǎo)流到父進(jìn)程的stream中(childProcess.stdout、childProcess.stderr),提供在父進(jìn)程中輸出子進(jìn)程消息的能力。
因此,解決此種問題可給子進(jìn)程的stdio重新賦值:
file: parent.js let cp = require('child_process'); const sp = cp.spawn('node',['./c.js'],{ stdio: [process.stdin,process.stdout,process.stderr] }); setTimeout(()=>{console.log('parent out')},5000); -------------- file: c.js setTimeout(()=>{ console.log('children exit'); },10000)
通過在parent.js中設(shè)置子進(jìn)程的stdio為當(dāng)前終端(其實(shí)繼承了父進(jìn)程的stdio),這樣父進(jìn)程在5s后退出,此時(shí)子進(jìn)程的ppid變?yōu)?,10s后子進(jìn)程退出。
上述實(shí)現(xiàn)只滿足“父進(jìn)程正常退出,子進(jìn)程成為守護(hù)進(jìn)程”的情況,一旦通過“ctrl+c”的方式終端父進(jìn)程,子進(jìn)程仍會(huì)退出,這還是與node底層實(shí)現(xiàn)有關(guān)。默認(rèn)“ctrl+c”觸發(fā)SIGINT信號(hào),父進(jìn)程接受信號(hào)后發(fā)送給子進(jìn)程,如果子進(jìn)程存在SIGINT偵聽函數(shù),則會(huì)執(zhí)行該函數(shù),否則執(zhí)行exit系統(tǒng)調(diào)用子進(jìn)程退出。因此,如果要讓子進(jìn)程在接收到SIGINT信號(hào)不退出,只需要不作處理即可:
file: c.js process.on('SIGINT',function(){ console.log('child sigint'); }); setTimeout(()=>{ console.log('children exit'); },10000)
以上實(shí)現(xiàn),可以滿足我們最初指定的目標(biāo):“父進(jìn)程退出或者中斷,子進(jìn)程仍正常運(yùn)行”。
實(shí)現(xiàn)二
node官方提供了創(chuàng)建daemon進(jìn)程的相關(guān)API,如果不仔細(xì)閱讀文檔還真不容易發(fā)現(xiàn)該特性。在child_process模塊中有個(gè)spawn函數(shù),通過spawn可以執(zhí)行shell命令及其相關(guān)選項(xiàng),同時(shí)spawn提供了創(chuàng)建子進(jìn)程的一些選項(xiàng),其中“detached”選項(xiàng)則與我們的需求密切相關(guān)。
detached選項(xiàng)可以讓node原生幫我們創(chuàng)建一個(gè)daemon進(jìn)程,設(shè)置datached為true可以創(chuàng)建一個(gè)新的session和進(jìn)程組,子進(jìn)程的pid為新創(chuàng)建進(jìn)程組的組pid,這與setsid起到相同的作用。此時(shí)的子進(jìn)程已經(jīng)和其父進(jìn)程屬于兩個(gè)session,因此父進(jìn)程的退出和中斷信號(hào)不會(huì)傳遞給子進(jìn)程,子進(jìn)程不會(huì)接受到父進(jìn)程的中斷信號(hào)自然也不會(huì)退出。當(dāng)父進(jìn)程結(jié)束之后,子進(jìn)程變?yōu)楣聝哼M(jìn)程從而被init進(jìn)程接收,ppid設(shè)置為1。
file: parent.js let cp = require('child_process'); const sp = cp.spawn('node',['./c.js'],{ detached: true, stdio: [process.stdin,process.stdout,process.stdout] }); sp.unref(); setTimeout(()=>{console.log('parent out')},5000); ---------------------- file: c.js setTimeout(()=>{ console.log('children exit'); },100000)
此時(shí),c.js文件并未設(shè)置SIGINT事件偵聽函數(shù),在父進(jìn)程中斷后仍會(huì)正常運(yùn)行,正是由于其和父進(jìn)程分屬于兩個(gè)session。
在parent.js文件中設(shè)置了sp.unref()函數(shù),目的是“避免父進(jìn)程等待子進(jìn)程退出”。那么為何會(huì)出現(xiàn)上述情況呢?這與node的事件循環(huán)有關(guān),讓父進(jìn)程的事件循環(huán)排除對(duì)ChildProcess子進(jìn)程對(duì)象的引用,可以使父進(jìn)程單獨(dú)退出。
總結(jié)
為什么上文介紹的兩個(gè)方法都可以實(shí)現(xiàn)daemon進(jìn)程呢?這還得回到系統(tǒng)層面進(jìn)行分析。在linux系統(tǒng)創(chuàng)建一個(gè)daemon進(jìn)程需要幾個(gè)步驟:
1.父進(jìn)程創(chuàng)建子進(jìn)程,父進(jìn)程退出,讓子進(jìn)程成為孤兒進(jìn)程,ppid=1
2.通過setsid命令或函數(shù)在子進(jìn)程中創(chuàng)建新的會(huì)話和進(jìn)程組
3.設(shè)置當(dāng)前目錄
4.設(shè)置文件權(quán)限,并關(guān)閉父進(jìn)程繼承打開的fd
所謂會(huì)話和進(jìn)程組,則是在linux多任務(wù)多用戶下的概念。不同會(huì)話的進(jìn)程無法通過通信,因此父子進(jìn)程相隔離。而執(zhí)行setsid命令則讓子進(jìn)程有了新的特性:
- 子進(jìn)程脫離父進(jìn)程所在的session控制,兩者獨(dú)立存在互不影響
- 子進(jìn)程脫離父進(jìn)程所在的進(jìn)程組
- 子進(jìn)程脫離原先的命令行終端,終端退出不影響子進(jìn)程
下面再回顧方法一與方法二的區(qū)別,發(fā)現(xiàn)方法一其實(shí)并不是真正的daemon進(jìn)程,只是通過偵聽相關(guān)中斷信號(hào)并設(shè)置nop函數(shù)(不執(zhí)行默認(rèn)的中斷行為)保證子進(jìn)程繼續(xù)運(yùn)行而已;而方法二則是標(biāo)準(zhǔn)的deamon進(jìn)程創(chuàng)建方式,優(yōu)先使用!
以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
詳解如何使用Node.js實(shí)現(xiàn)熱重載頁面
這篇文章主要介紹了詳解如何使用Node.js實(shí)現(xiàn)熱重載頁面,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-05-05node.js同步/異步文件讀寫-fs,Stream文件流操作實(shí)例詳解
這篇文章主要介紹了node.js同步/異步文件讀寫-fs,Stream文件流操作,結(jié)合實(shí)例形式詳細(xì)分析了node.js針對(duì)文件的同步/異步讀寫與文件流相關(guān)操作技巧,需要的朋友可以參考下2023-06-06jwt在node中的應(yīng)用實(shí)踐(安裝配置封裝)
這篇文章主要為大家介紹了jwt在node中的應(yīng)用實(shí)踐包括安裝配置封裝,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09如何構(gòu)建一個(gè)?NodeJS?影院微服務(wù)并使用?Docker?部署
微服務(wù)是一個(gè)單獨(dú)的自包含單元,與其他許多單元一起構(gòu)成一個(gè)大型應(yīng)用程序,這篇文章主要介紹了如何構(gòu)建一個(gè)NodeJS影院微服務(wù)并使用Docker部署,在這個(gè)系列中,將構(gòu)建一個(gè) NodeJS 微服務(wù),并使用 Docker Swarm 集群進(jìn)行部署,需要的朋友可以參考下2023-08-08基于nodejs res.end和res.send的區(qū)別
今天小編就為大家分享一篇基于nodejs res.end和res.send的區(qū)別,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-05-05