熟悉js 的朋友都知道, js 是单线程
的,在Node 中,採用的是多行程單執行緒的模型。由於javascript單執行緒的限制,在多核心伺服器上,我們往往需要啟動多個進程才能最大化伺服器效能。
Node.js 進程群集可用於執行多個Node.js 實例,這些實例可以在其應用程式執行緒之間指派工作負載。 當不需要進程隔離時,請改用worker_threads
模組,它允許在單一Node.js 實例中執行多個應用程式執行緒。
Node 在V0.8 版本之後引進了cluster模組,透過一个主进程(master) 管理多个子进程(worker) 的方式实现集群
。
叢集模組可以輕鬆建立共用伺服器連接埠的子進程。
cluster 底層是child_process 模組,除了可以傳送普通訊息,還可以傳送底層物件
TCP
、UDP
等,cluster
模組是child_process
模組和net
模組的組合應用程式。 cluster 啟動時,內部會啟動TCP 伺服器,將這個TCP 伺服器端socket 的檔案描述子發給工作進程。
在cluster
模組應用中,一个主进程只能管理一组工作进程
,其運作模式沒有child_process
模組那麼靈活,但是更穩定:
const cluster = require('cluster')複
.isMaster
標識主進程, Node<16.isPrimary
標識主進程, Node>16.isWorker
標識子進程.worker
對當前工作進程物件的引用【子進程中】.workers
儲存活動工作進程物件的哈希,以id
字段為鍵。 這樣可以很容易地遍歷所有工作進程。 它僅在主進程中可用。 cluster.wokers[id] === worker
【主進程中】.settings
只讀, cluster配置項。在呼叫.setupPrimary()或.fork()方法之後,此設定物件將包含設置,包括預設值。之前為空物件。此物件不應手動變更或設定。cluster.settings
配置項目詳情:- `execArgv` <string[]>傳給Node.js 執行檔的字串參數清單。 **預設值:** `process.execArgv`。 - `exec` <string> 工作進程檔案的檔案路徑。 **預設值:** `process.argv[1]`。 - `args` <string[]> 傳給工作進程的字串參數。 **預設值:**`process.argv.slice(2)`。 - `cwd` <string>工作進程的目前工作目錄。 **預設值:** `undefined` (從父行程繼承)。 - `serialization` <string>指定用於在行程之間傳送訊息的序列化類型。 可能的值為`'json'` 和`'advanced'`。 **預設值:** `false`。 - `silent` <boolean>是否將輸出傳送至父行程的標準輸入輸出。 **預設值:** `false`。 - `stdio` <Array>配置衍生進程的標準輸入輸出。 由於叢集模組依賴IPC 來運行,因此此配置必須包含`'ipc'` 條目。 提供此選項時,它會覆蓋`silent`。 - `uid` <number>設定進程的使用者識別碼。 - `gid` <number>設定進程的群組識別。 - `inspectPort` <number> | <Function> 設定工作進程的檢查器連接埠。 這可以是數字,也可以是不帶參數並傳回數字的函數。 預設情況下,每個工作進程都有自己的端口,從主進程的`process.debugPort` 開始遞增。 - `windowsHide` <boolean> 隱藏通常在Windows 系統上建立的衍生程序控制台視窗。 **預設值:** `false`。
.fork([env])
衍生新的工作程序【主程序中】.setupPrimary([settings])
Node>16.setupMaster([settings])
用於更改預設的'fork' 行為,用後設定將出現在cluster.settings
。任何設定更改只會影響未來對.fork()
的調用,而不會影響已經運行的工作進程。上述預設值僅適用於第一次呼叫。 Node 小於16【主進程中】.disconnect([callback])
當所有工作進程斷開連接並關閉句柄時調用【主進程中】為了讓集群更加穩定和健壯, cluster
模組也暴露了許多事件:
'message'
事件, 當叢集主進程接收到來自任何工作進程的訊息時觸發。'exit'
事件, 當任何工作進程死亡時,則叢集模組將觸發'exit'
事件。cluster.on('exit', (worker, code, signal) => { console.log('worker %d died (%s). restarting...', worker.process.pid, signal || code); cluster.fork(); });
'listening'
事件,從工作程序呼叫listen()
後,當伺服器上觸發'listening'
事件時,則主程序中的cluster
也會觸發'listening'
事件。cluster.on('listening', (worker, address) => { console.log( `A worker is now connected to ${address.address}:${address.port}`); });
'fork'
事件,當新的工作進程被衍生時,則叢集模組將觸發'fork'
事件。cluster.on('fork', (worker) => { timeouts[worker.id] = setTimeout(errorMsg, 2000); });
'setup'
事件,每次呼叫.setupPrimary()
時觸發。disconnect
事件,在工作進程IPC 通道斷開連線後觸發。 當工作進程正常退出、被殺死、或手動斷開連接時cluster.on('disconnect', (worker) => { console.log(`The worker #${worker.id} has disconnected`); });
Worker
物件包含了工作進程的所有公共的資訊和方法。 在主進程中,可以使用cluster.workers
來取得它。 在工作進程中,可以使用cluster.worker
來取得它。
.id
工作進程標識,每個新的工作進程都被賦予了自己唯一的id,此id 儲存在id
。當工作進程存活時,這是在cluster.workers
中索引它的鍵。.process
所有工作進程都是使用child_process.fork()
創建,此函數傳回的物件儲存為.process
。 在工作進程中,儲存了全域的process
。.send(message[, sendHandle[, options]][, callback])
向工作進程或主進程發送訊息,可選擇使用句柄。在主進程中,這會向特定的工作進程發送訊息。 它與ChildProcess.send()
相同。在工作進程中,這會向主進程發送訊息。 它與process.send()
相同。.destroy()
.kill([signal])
此函數會殺死工作進程。 kill()
函數在不等待正常斷開連接的情況下殺死工作進程,它與worker.process.kill()
具有相同的行為。為了向後相容,此方法別名為worker.destroy()
。.disconnect([callback])
傳送給工作進程,使其呼叫自身的.disconnect()
將關閉所有伺服器,等待那些伺服器上的'close'
事件,然後斷開IPC 通道。.isConnect()
如果工作進程透過其IPC 通道連接到其主進程,則此函數傳回true
,否則傳回false
。 工作進程在建立後連接到其主進程。.isDead()
如果工作進程已終止(由於退出或收到訊號),則此函數傳回true
。 否則,它會傳回false
。為了讓群集更加穩定和健壯, cluster
模組也暴露了許多事件:
'message'
事件, 在工作進程中。cluster.workers[id].on('message', messageHandler);
'exit'
事件, 當任何工作進程死亡時,則当前worker工作进程
物件將觸發'exit'
事件。if (cluster.isPrimary) { const worker = cluster.fork(); worker.on('exit', (code, signal) => { if (signal) { console.log(`worker was killed by signal: ${signal}`); } else if (code !== 0) { console.log(`worker exited with error code: ${code}`); } else { console.log('worker success!'); } }); }
'listening'
事件,從工作流程呼叫listen()
,對目前工作進程進行監聽。cluster.fork().on('listening', (address) => { // 工作進程正在監聽});
disconnect
事件,在工作進程IPC 通道斷開連線後觸發。 當工作進程正常退出、被殺死、或手動斷開連接時cluster.fork().on('disconnect', () => { //限定於目前worker物件觸發});
Node中主進程與子進程之間透過進程間通訊(IPC) 實現進程間的通信,進程間透過.send()
(a.send表示向a發送)方法發送訊息,監聽message
事件收取訊息,這是cluster模块
透過整合EventEmitter
來實現的。還是一個簡單的官網的進程間通訊範例
process.on('message')
、 process.send()
child.on('message')
、 child.send()
# cluster.isMaster # cluster.fork() # cluster.workers # cluster.workers[id].on('message', messageHandler); # cluster.workers[id].send(); # process.on('message', messageHandler); # process.send(); const cluster = require('cluster'); const http = require('http'); # 主程序if (cluster.isMaster) { // Keep track of http requests console.log(`Primary ${process.pid} is running`); let numReqs = 0; // Count requests function messageHandler(msg) { if (msg.cmd && msg.cmd === 'notifyRequest') { numReqs += 1; } } // Start workers and listen for messages containing notifyRequest // 開啟多進程(cpu核心數) // 衍生工作進程。 const numCPUs = require('os').cpus().length; for (let i = 0; i < numCPUs; i++) { console.log(i) cluster.fork(); } // cluster worker 主程序與子程序通訊for (const id in cluster.workers) { // ***監聽來自子程序的事件cluster.workers[id].on('message', messageHandler); // ***傳送cluster.workers[id].send({ type: 'masterToWorker', from: 'master', data: { number: Math.floor(Math.random() * 50) } }); } cluster.on('exit', (worker, code, signal) => { console.log(`worker ${worker.process.pid} died`); }); } else { # 子程序// 工作進程可以共用任何TCP 連線// 在本範例中,其是HTTP 伺服器// Worker processes have a http server. http.Server((req, res) => { res.writeHead(200); res.end('hello worldn'); //****** ! ! ! ! Notify master about the request ! ! ! ! ! ! ******* //****** 傳送process.send({ cmd: 'notifyRequest' }); //****** 監聽從process來的process.on('message', function(message) { // xxxxxxx }) }).listen(8000); console.log(`Worker ${process.pid} started`); }
NodeJS 進程之間通訊只有訊息傳遞,不會真正的傳遞物件。
send()
方法在發送訊息前,會將訊息組裝成handle 和message,這個message 會經過JSON.stringify
序列化,也就是說,傳遞句柄的時候,不會將整個物件傳遞過去,在IPC 通道傳輸的都是字串,傳輸後透過JSON.parse
還原成物件。
程式碼裡有app.listen(port)
在進行fork 時,為什麼多個進程可以監聽同一個連接埠?
原因是主程序透過send() 方法向多個子程序發送屬於該主程序的一個服務對象的句柄,所以對於每一個子程序而言,它們在還原句柄之後,得到的服務對像是一樣的,當網絡請求向服務端發起時,進程服務是搶佔式的,所以監聽相同連接埠時不會造成異常。
# master.js const fork = require('child_process').fork; const cpus = require('os').cpus(); for (let i=0; i<cpus.length; i++) { const worker = fork('worker.js'); console.log('worker process created, pid: %s ppid: %s', worker.pid, process.pid); }
# worker.js const http = require('http'); http.createServer((req, res) => { res.end('I am worker, pid: ' + process.pid + ', ppid: ' + process.ppid); }).listen(3000);
以上程式碼範例,控制台執行
node master.js
只有一個worker 可以監聽到3000 端口,其餘將會拋出Error: listen EADDRINUSE :::3000
錯誤。
发送句柄
功能/** * http://nodejs.cn/api/child_process.html#child_process_subprocess_send_message_sendhandle_options_callback * message * sendHandle */ subprocess.send(message, sendHandle)
當父子進程之間建立IPC 通道之後,透過子進程物件的send 方法發送訊息,二个参数sendHandle 就是句柄,可以是TCP套接字、TCP服务器、UDP套接字等
,為了解決上面多進程連接埠佔用問題,我們將主進程的socket 傳遞到子進程。
# master.js const fork = require('child_process').fork; const cpus = require('os').cpus(); const server = require('net').createServer(); server.listen(3000); process.title = 'node-master' for (let i=0; i<cpus.length; i++) { const worker = fork('worker.js'); # 句柄傳遞worker.send('server', server); console.log('worker process created, pid: %s ppid: %s', worker.pid, process.pid); }
// worker.js let worker; process.title = 'node-worker' process.on('message', function (message, sendHandle) { if (message === 'server') { worker = sendHandle; worker.on('connection', function (socket) { console.log('I am worker, pid: ' + process.pid + ', ppid: ' + process.ppid) }); } });
驗證一番,控制台執行node master.js
了解cluster
的話會知道,子進程是透過cluster.fork()
創建的。在linux 中,系統原生提供了fork
方法,那麼為什麼Node 選擇自己實作cluster模块
,而不是直接使用系統原生的方法?主要的原因是以下兩點:
fork的進程監聽同一埠會導致連接埠佔用錯誤
fork的進程之間沒有負載平衡,容易導致驚群現
像在cluster模块
中,針對第一個問題,透過判斷目前進程是否為master进程
,若是,則監聽端口,若不是則表示為fork 的worker进程
,不監聽端口。
針對第二個問題, cluster模块
內建了負載平衡功能, master进程
負責監聽埠接收請求,然後透過調度演算法(預設為Round-Robin,可以透過環境變數NODE_CLUSTER_SCHED_POLICY
修改調度演算法)分配給對應的worker进程
。
當程式碼拋出了異常沒有被捕獲到時,進程將會退出,此時Node.js 提供了process.on('uncaughtException', handler)
接口來捕獲它,但是當一個Worker 進程遇到未捕獲的異常時,它已經處於一個不確定狀態,此時我們應該讓這個進程優雅退出:
+---------+ +---------+ | Worker | | Master | +---------+ +----+----+ | uncaughtException | +------------+ | | | | +---------+ | <----------+ | | Worker | | | +----+----+ | disconnect | fork a new worker | +-------------------------> + ---------------------> | | wait... | | | exit | | +-------------------------> | | | | | die | | | | | |
當一個進程出現異常導致crash 或OOM 被系統殺死時,不像未捕獲異常發生時我們還有機會讓進程繼續執行,只能夠讓當前進程直接退出,Master 立刻fork 一個新的Worker。
child_process 模組提供了衍生子程序的能力, 簡單來說就是执行cmd命令的能力
。 預設情況下, stdin、 stdout 和stderr 的管道会在父Node.js 进程和衍生的子进程之间建立
。 這些管道具有有限的(且平台特定的)容量。 如果子進程寫入stdout 時超出該限制且沒有擷取輸出,則子進程會阻塞並等待管道緩衝區接受更多的資料。 這與shell 中的管道的行為相同。 如果不消費輸出,則使用{ stdio: 'ignore' } 選項。
const cp = require('child_process');
透過API 建立出來的子程序和父進程沒有任何必然聯繫
4個非同步方法,建立子程序:fork、exec、execFile、spawn
Node
fork(modulePath, args)
:想將一個Node 進程作為一個獨立的進程來運行的時候使用,使得計算處理和文件描述器脫離Node 主進程(複製一個子進程)非Node
spawn(command, args)
:處理一些會有很多子行程I/O 時、行程會有大量輸出時使用execFile(file, args[, callback])
:只要執行一個外部程式的時候使用,執行速度快,處理使用者輸入相對安全exec(command, options)
:想直接存取執行緒的shell 指令時使用,一定要注意使用者輸入3個同步方法: execSync
、 execFileSync
、 spawnSync
其他三種方法都是spawn()
的延伸。
記住,衍生的Node.js 子進程獨立於父進程,但兩者之間建立的IPC 通訊通道除外。 每個行程都有自己的內存,帶有自己的V8 實例
舉個?
在一個目錄下新建worker.js 和master.js 兩個檔案:
# child.js const t = JSON.parse(process.argv[2]); console.error(`子進程t=${JSON.stringify(t)}`); process.send({hello:`兒子pid=${process.pid} 給爸爸程序pid=${process.ppid} 請安`}); process.on('message', (msg)=>{ console.error(`子進程msg=${JSON.stringify(msg)}`); });
# parent.js const {fork} = require('child_process'); for(let i = 0; i < 3; i++){ const p = fork('./child.js', [JSON.stringify({id:1,name:1})]); p.on('message', (msg) => { console.log(`messsgae from child msg=${JSON.stringify(msg)}`, ); }); p.send({hello:`來自爸爸${process.pid} 進程id=${i}的問候`}); }
透過node parent.js
啟動parent.js,然後透過ps aux | grep worker.js
查看進程的數量,我們可以發現,理想狀況下,進程的數量等於CPU 的核心數,每個進程各自利用一個CPU 核心。
這是經典的Master-Worker 模式(主從模式)
實際上,fork 進程是昂貴的,複製進程的目的是充分利用CPU 資源,所以NodeJS 在單執行緒上使用了事件驅動的方式來解決高並發的問題。
適用場景<br/>一般用於比較耗時的場景,並且用node去實現的,例如下載檔案;
fork可以實現多執行緒下載:將檔案分成多塊,然後每個行程下載一部分,最後拼起來;
const cp = require('child_process'); // 第一個參數,要執行的可執行檔的名稱或路徑。這裡是echo cp.execFile('echo', ['hello', 'world'], (err, stdout, stderr) => { if (err) { console.error(err); } console.log('stdout: ', stdout); console.log('stderr: ', stderr); });
適用場景<br/>比較適合開銷小的任務,更專注於結果,如 ls等;
主要用來執行一個shell方法,其內部還是呼叫了spawn ,不過他有最大快取限制。
const cp = require('child_process'); cp.exec(`cat ${__dirname}/messy.txt | sort | uniq`, (err, stdout, stderr) => { console.log(stdout); });
適用場景<br/>比較適合開銷小的任務,更關注結果,例如ls等;
單一任務
const cp = require('child_process'); const child = cp.spawn('echo', ['hello', 'world']); child.on('error', console.error); # 輸出是流,輸出到主程序stdout,控制台child.stdout.pipe(process.stdout); child.stderr.pipe(process.stderr);
多任務串聯
const cp = require('child_process'); const path = require('path'); const cat = cp.spawn('cat', [path.resolve(__dirname, 'messy.txt')]); const sort = cp.spawn('sort'); const uniq = cp.spawn('uniq'); # 輸出是流cat.stdout.pipe(sort.stdin); sort.stdout.pipe(uniq.stdin); uniq.stdout.pipe(process.stdout);
適用場景
spawn是流式的,所以適合耗時任務,例如執行npm install,打印install的過程
在進程已結束並且子進程的標準輸入輸出流(sdtio)已關閉之後,則觸發'close'
事件。這個事件跟exit
不同,因為多個進程可以共享同一個stdio流。
參數:
問題:code一定是有的嗎?
(從對code的註解來看好像不是)例如用kill
殺死子進程,那麼,code是?
參數:
code、signal,如果子行程是自己退出的,那麼code
就是退出碼,否則為null;
如果子進程是透過訊號結束的,那麼, signal
就是結束進程的訊號,否則為null。
這兩者中,一者肯定不是null。
注意事項:
exit
事件觸發時,子程序的stdio stream可能還開啟著。 (場景?)此外,nodejs監聽了SIGINT和SIGTERM訊號,也就是說,nodejs收到這兩個訊號時,不會立刻退出,而是先做一些清理的工作,然後重新拋出這兩個訊號。 (目測此時js可以做清理工作了,例如關閉資料庫等。)
SIGINT
:interrupt,程式終止訊號,通常在使用者按下CTRL+C時發出,用來通知前台進程終止進程。
SIGTERM
:terminate,程式結束訊號,該訊號可以被阻塞和處理,通常用來要求程式自行正常退出。 shell命令kill缺省產生這個訊號。如果訊號終止不了,我們才會嘗試SIGKILL(強制終止)。
當發生下列事情時,error就會被觸發。當error觸發時,exit可能觸發,也可能不會觸發。 (內心是崩潰的)
當採用process.send()
來傳送訊息時觸發。
參數:
message
,為json對象,或primitive value; sendHandle
,net.Socket對象,或 net.Server物件(熟悉cluster的同學應該對這個不陌生)
.connected :當呼叫.disconnected()
時,設為false。代表是否能夠從子進程接收訊息,或對子進程發送訊息。
.disconnect() :關閉父行程、子行程之間的IPC通道。當這個方法被呼叫時, disconnect
事件就會被觸發。如果子進程是node實例(透過child_process.fork()建立),那麼在子進程內部也可以主動呼叫process.disconnect()
來終止IPC通道。
應對單線程問題,通常使用多進程的方式來模擬多線程
Node 進程佔用了7 個執行緒
Node 中最核心的是v8 引擎,在Node 啟動後,會創建v8 的實例,這個實例是多執行緒的主執行緒
JavaScript 的執行是單執行单线程
,但Javascript 的宿主環境,無論是Node 還是瀏覽器都是多執行緒的。
Javascript 為什麼是單線程?
這個問題需要從瀏覽器說起,在瀏覽器環境中對於DOM 的操作,試想如果多個線程來對同一個DOM 操作是不是就亂了呢,那也就意味著對於DOM的操作只能是單線程,避免DOM 渲染衝突。在瀏覽器環境中UI 渲染線程和JS 執行引擎是互斥的,一方在執行時都會導致另一方被掛起,這是由JS 引擎決定的。
process.env.UV_THREADPOOL_SIZE = 64
worker_threads
給Node 提供真正的多執行緒能力const { isMainThread, parentPort, workerData, threadId, MessageChannel, MessagePort, Worker } = require('worker_threads'); function mainThread() { for (let i = 0; i < 5; i++) { const worker = new Worker(__filename, { workerData: i }); worker.on('exit', code => { console.log(`main: worker stopped with exit code ${code}`); }); worker.on('message', msg => { console.log(`main: receive ${msg}`); worker.postMessage(msg + 1); }); } } function workerThread() { console.log(`worker: workerDate ${workerData}`); parentPort.on('message', msg => { console.log(`worker: receive ${msg}`); }), parentPort.postMessage(workerData); } if (isMainThread) { mainThread(); } else { workerThread(); }
const assert = require('assert'); const { Worker, MessageChannel, MessagePort, isMainThread, parentPort } = require('worker_threads'); if (isMainThread) { const worker = new Worker(__filename); const subChannel = new MessageChannel(); worker.postMessage({ hereIsYourPort: subChannel.port1 }, [subChannel.port1]); subChannel.port2.on('message', (value) => { console.log('received:', value); }); } else { parentPort.once('message', (value) => { assert(value.hereIsYourPort instanceof MessagePort); value.hereIsYourPort.postMessage('the worker is sending this'); value.hereIsYourPort.close(); }); }
進程是資源分配的最小單位,執行緒是CPU調度的最小單位
IPC (Inter-process communication) 即进程间通信
,由於每個進程創建之後都有自己的獨立地址空間,實現IPC 的目的就是為了進程之間資源共享訪問。
實作IPC 的方式有多種:管道、訊息佇列、訊號量、Domain Socket,Node.js 透過pipe 來實現。
實際上,父進程會在創建子進程之前,會先建立IPC 通道並監聽這個IPC,然後再建立子進程,透過環境變數(NODE_CHANNEL_FD)告訴子進程和IPC 通道相關的檔案描述符,子進程啟動的時候根據檔案描述子連接IPC 通道,從而和父進程建立連線。
句柄是一種可以用來識別資源的引用的,它的內部包含了指向物件的檔案資源描述符。
一般情況下,當我們想要將多個進程監聽到一個連接埠下,可能會考慮使用主進程代理程式的方式處理:
然而,這種代理方案會導致每次請求的接收和代理轉發用掉兩個文件描述符,而係統的文件描述符是有限的,這種方式會影響系統的擴展能力。
所以,為什麼要使用句柄?原因是在實際應用場景下,建立IPC 通訊後可能會涉及到比較複雜的資料處理場景,句柄可以作為send()
方法的第二個可選參數傳入,也就是說可以直接將資源的識別通過IPC 傳輸,避免了上面所說的代理轉送造成的文件描述符的使用。
以下是支援發送的句柄類型:
父進程創建子進程之後,父進程退出了,但是父進程對應的一個或多個子進程還在運行,這些子進程會被系統的init 進程收養,對應的進程ppid 為1,這就是孤兒進程。透過以下程式碼範例說明。
# worker.js const http = require('http'); const server = http.createServer((req, res) => { res.end('I am worker, pid: ' + process.pid + ', ppid: ' + process.ppid); // 記錄目前工作流程pid 及父行程ppid }); let worker; process.on('message', function (message, sendHandle) { if (message === 'server') { worker = sendHandle; worker.on('connection', function(socket) { server.emit('connection', socket); }); } });
# master.js const fork = require('child_process').fork; const server = require('net').createServer(); server.listen(3000); const worker = fork('worker.js'); worker.send('server', server); console.log('worker process created, pid: %s ppid: %s', worker.pid, process.pid); process.exit(0); // 建立子程序之後,主程序退出,此時建立的worker 程序會成為孤兒程序
控制台進行測試,輸出目前工作程序pid 和父程序ppid
由於在master.js 裡退出了父進程,活動監視器所顯示的也只有工作進程。
再次驗證,打開控制台調用接口,可以看到工作進程5611 對應的ppid 為1(為init 進程),此時已經成為了孤兒進程
守護程式運行在後台不受終端的影響,什麼意思呢?
Node.js 開發的同學可能熟悉,當我們打開終端執行node app.js
開啟一個服務進程之後,這個終端就會一直被佔用,如果關掉終端,服務就會斷掉,即前台运行模式
。
如果採用守護程式進程方式,這個終端我執行node app.js
開啟一個服務進程之後,我還可以在這個終端上做些別的事情,且不會互相影響。
建立子程序
在子程序中建立新會話(呼叫系統函式setsid)
改變子程序工作目錄(如:「/」 或「/usr/ 等)
父行程終止
options.detached
index.js const spawn = require('child_process').spawn; function startDaemon() { const daemon = spawn('node', ['daemon.js'], { cwd: '/usr', detached : true, stdio: 'ignore', }); console.log('守護程式開啟父程式pid: %s, 守護程式pid: %s', process.pid, daemon.pid); daemon.unref(); } startDaemon()
daemon.js 檔案裡處理邏輯開啟一個計時器每10 秒執行一次,使得這個資源不會退出,同時寫入日誌到子進程目前工作目錄下
/usr/daemon.js const fs = require('fs'); const { Console } = require('console'); // custom simple logger const logger = new Console(fs.createWriteStream('./stdout.log'), fs.createWriteStream('./stderr.log')); setInterval(function() { logger.log('daemon pid: ', process.pid, ', ppid: ', process.ppid); }, 1000 * 10);
守護程式實作Node.js 版本原始碼位址
https://github.com/Q-Angelo/project-training/tree/master/nodejs/simple-daemon
在實際工作中對於守護進程並不陌生,例如PM2、Egg-Cluster 等,以上只是一個簡單的Demo 對守護進程做了一個說明,在實際工作中對守護進程的健壯性要求還是很高的,例如:進程的異常監聽、工作進程管理調度、進程掛掉之後重啟等等,這些還需要不斷思考。
目錄是什麼?
進程的當前工作目錄可以透過process.cwd()
命令獲取,預設為當前啟動的目錄,如果是創建子進程則繼承於父進程的目錄,可透過process.chdir()
指令重置,例如透過spawn 指令建立的子程序可以指定cwd 選項設定子程序的工作目錄。
有什麼作用?
例如,透過fs 讀取文件,如果設定為相對路徑則相對於當前進程啟動的目錄進行查找,所以,啟動目錄設定有誤的情況下將無法得到正確的結果。還有一種情況程式裡引用第三方模組也是根據目前進程啟動的目錄來進行查找的。
// 範例process.chdir('/Users/may/Documents/test/') // 設定目前行程目錄console.log(process.cwd()); // 取得目前行程目錄