七天學會NodeJS(程序管理)
程序管理
NodeJS可以感知和控制自身程序的執行環境和狀態,也可以建立子程序並與其協同工作,這使得NodeJS可以把多個程式組合在一起共同完成某項工作,並在其中充當膠水和排程器的作用。本章除了介紹與之相關的NodeJS內建模組外,還會重點介紹典型的使用場景。
開門紅
我們已經知道了NodeJS自帶的fs
模組比較基礎,把一個目錄裡的所有檔案和子目錄都拷貝到另一個目錄裡需要寫不少程式碼。另外我們也知道,終端下的cp
命令比較好用,一條cp -r source/* target
命令就能搞定目錄拷貝。那我們首先看看如何使用NodeJS呼叫終端命令來簡化目錄拷貝,示例程式碼如下:
var child_process = require('child_process'); var util = require('util'); function copy(source, target, callback) { child_process.exec( util.format('cp -r %s/* %s', source, target), callback); } copy('a', 'b', function (err) { // ... });
從以上程式碼中可以看到,子程序是非同步執行的,通過回撥函式返回執行結果。
API走馬觀花
我們先大致看看NodeJS提供了哪些和程序管理有關的API。這裡並不逐一介紹每個API的使用方法,官方文件已經做得很好了。
Process
任何一個程序都有啟動程序時使用的命令列引數,有標準輸入標準輸出,有執行許可權,有執行環境和執行狀態。在NodeJS中,可以通過process
物件感知和控制NodeJS自身程序的方方面面。另外需要注意的是,process
不是內建模組,而是一個全域性物件,因此在任何地方都可以直接使用。
Child Process
使用child_process
模組可以建立和控制子程序。該模組提供的API中最核心的是.spawn
,其餘API都是針對特定使用場景對它的進一步封裝,算是一種語法糖。
Cluster
cluster
模組是對child_process
模組的進一步封裝,專用於解決單程序NodeJS Web伺服器無法充分利用多核CPU的問題。使用該模組可以簡化多程序伺服器程式的開發,讓每個核上執行一個工作程序,並統一通過主程序監聽埠和分發請求。
應用場景
和程序管理相關的API單獨介紹起來比較枯燥,因此這裡從一些典型的應用場景出發,分別介紹一些重要API的使用方法。
如何獲取命令列引數
在NodeJS中可以通過process.argv
獲取命令列引數。但是比較意外的是,node
執行程式路徑和主模組檔案路徑固定佔據了argv[0]
和argv[1]
兩個位置,而第一個命令列引數從argv[2]
開始。為了讓argv
使用起來更加自然,可以按照以下方式處理。
function main(argv) {
// ...
}
main(process.argv.slice(2));
如何退出程式
通常一個程式做完所有事情後就正常退出了,這時程式的退出狀態碼為0
。或者一個程式執行時發生了異常後就掛了,這時程式的退出狀態碼不等於0
。如果我們在程式碼中捕獲了某個異常,但是覺得程式不應該繼續執行下去,需要立即退出,並且需要把退出狀態碼設定為指定數字,比如1
,就可以按照以下方式:
try {
// ...
} catch (err) {
// ...
process.exit(1);
}
如何控制輸入輸出
NodeJS程式的標準輸入流(stdin)、一個標準輸出流(stdout)、一個標準錯誤流(stderr)分別對應process.stdin
、process.stdout
和process.stderr
,第一個是隻讀資料流,後邊兩個是隻寫資料流,對它們的操作按照對資料流的操作方式即可。例如,console.log
可以按照以下方式實現。
function log() {
process.stdout.write(
util.format.apply(util, arguments) + '\n');
}
如何降權
在Linux系統下,我們知道需要使用root許可權才能監聽1024以下埠。但是一旦完成埠監聽後,繼續讓程式執行在root許可權下存在安全隱患,因此最好能把許可權降下來。以下是這樣一個例子。
http.createServer(callback).listen(80, function () {
var env = process.env,
uid = parseInt(env['SUDO_UID'] || process.getuid(), 10),
gid = parseInt(env['SUDO_GID'] || process.getgid(), 10);
process.setgid(gid);
process.setuid(uid);
});
上例中有幾點需要注意:
-
如果是通過
sudo
獲取root許可權的,執行程式的使用者的UID和GID儲存在環境變數SUDO_UID
和SUDO_GID
裡邊。如果是通過chmod +s
方式獲取root許可權的,執行程式的使用者的UID和GID可直接通過process.getuid
和process.getgid
方法獲取。 -
process.setuid
和process.setgid
方法只接受number
型別的引數。 -
降權時必須先降GID再降UID,否則順序反過來的話就沒許可權更改程式的GID了。
如何建立子程序
以下是一個建立NodeJS子程序的例子。
var child = child_process.spawn('node', [ 'xxx.js' ]);
child.stdout.on('data', function (data) {
console.log('stdout: ' + data);
});
child.stderr.on('data', function (data) {
console.log('stderr: ' + data);
});
child.on('close', function (code) {
console.log('child process exited with code ' + code);
});
上例中使用了.spawn(exec, args, options)
方法,該方法支援三個引數。第一個引數是執行檔案路徑,可以是執行檔案的相對或絕對路徑,也可以是根據PATH環境變數能找到的執行檔名。第二個引數中,陣列中的每個成員都按順序對應一個命令列引數。第三個引數可選,用於配置子程序的執行環境與行為。
另外,上例中雖然通過子程序物件的.stdout
和.stderr
訪問子程序的輸出,但通過options.stdio
欄位的不同配置,可以將子程序的輸入輸出重定向到任何資料流上,或者讓子程序共享父程序的標準輸入輸出流,或者直接忽略子程序的輸入輸出。
程序間如何通訊
在Linux系統下,程序之間可以通過訊號互相通訊。以下是一個例子。
/* parent.js */
var child = child_process.spawn('node', [ 'child.js' ]);
child.kill('SIGTERM');
/* child.js */
process.on('SIGTERM', function () {
cleanUp();
process.exit(0);
});
在上例中,父程序通過.kill
方法向子程序傳送SIGTERM
訊號,子程序監聽process
物件的SIGTERM
事件響應訊號。不要被.kill
方法的名稱迷惑了,該方法本質上是用來給程序傳送訊號的,程序收到訊號後具體要做啥,完全取決於訊號的種類和程序自身的程式碼。
另外,如果父子程序都是NodeJS程序,就可以通過IPC(程序間通訊)雙向傳遞資料。以下是一個例子。
/* parent.js */
var child = child_process.spawn('node', [ 'child.js' ], {
stdio: [ 0, 1, 2, 'ipc' ]
});
child.on('message', function (msg) {
console.log(msg);
});
child.send({ hello: 'hello' });
/* child.js */
process.on('message', function (msg) {
msg.hello = msg.hello.toUpperCase();
process.send(msg);
});
可以看到,父程序在建立子程序時,在options.stdio
欄位中通過ipc
開啟了一條IPC通道,之後就可以監聽子程序物件的message
事件接收來自子程序的訊息,並通過.send
方法給子程序傳送訊息。在子程序這邊,可以在process
物件上監聽message
事件接收來自父程序的訊息,並通過.send
方法向父程序傳送訊息。資料在傳遞過程中,會先在傳送端使用JSON.stringify
方法序列化,再在接收端使用JSON.parse
方法反序列化。
如何守護子程序
守護程序一般用於監控工作程序的執行狀態,在工作程序不正常退出時重啟工作程序,保障工作程序不間斷執行。以下是一種實現方式。
/* daemon.js */
function spawn(mainModule) {
var worker = child_process.spawn('node', [ mainModule ]);
worker.on('exit', function (code) {
if (code !== 0) {
spawn(mainModule);
}
});
}
spawn('worker.js');
可以看到,工作程序非正常退出時,守護程序立即重啟工作程序。
小結
本章介紹了使用NodeJS管理程序時需要的API以及主要的應用場景,總結起來有以下幾點:
-
使用
process
物件管理自身。 -
使用
child_process
模組建立和管理子程序。