第二次迭代之后,服務器本身的功能和性能已經得到了初步滿足。接下來我們需要從穩定性的角度重新審視一下代碼,看看還需要做些什么。
## 設計
從工程角度上講,沒有絕對可靠的系統。即使第二次迭代的代碼經過反復檢查后能確保沒有bug,也很難說是否會因為NodeJS本身,或者是操作系統本身,甚至是硬件本身導致我們的服務器程序在某一天掛掉。因此一般生產環境下的服務器程序都配有一個守護進程,在服務掛掉的時候立即重啟服務。一般守護進程的代碼會遠比服務進程的代碼簡單,從概率上可以保證守護進程更難掛掉。如果再做得嚴謹一些,甚至守護進程自身可以在自己掛掉時重啟自己,從而實現雙保險。
因此在本次迭代時,我們先利用NodeJS的進程管理機制,將守護進程作為父進程,將服務器程序作為子進程,并讓父進程監控子進程的運行狀態,在其異常退出時重啟子進程。
## 實現
根據以上設計,我們編寫了守護進程需要的代碼。
~~~
var cp = require('child_process');
var worker;
function spawn(server, config) {
worker = cp.spawn('node', [ server, config ]);
worker.on('exit', function (code) {
if (code !== 0) {
spawn(server, config);
}
});
}
function main(argv) {
spawn('server.js', argv[0]);
process.on('SIGTERM', function () {
worker.kill();
process.exit(0);
});
}
main(process.argv.slice(2));
~~~
此外,服務器代碼本身的入口函數也要做以下調整。
~~~
function main(argv) {
var config = JSON.parse(fs.readFileSync(argv[0], 'utf-8')),
root = config.root || '.',
port = config.port || 80,
server;
server = http.createServer(function (request, response) {
...
}).listen(port);
process.on('SIGTERM', function () {
server.close(function () {
process.exit(0);
});
});
}
~~~
我們可以把守護進程的代碼保存為`daemon.js`,之后我們可以通過`node daemon.js config.json`啟動服務,而守護進程會進一步啟動和監控服務器進程。此外,為了能夠正常終止服務,我們讓守護進程在接收到`SIGTERM`信號時終止服務器進程。而在服務器進程這一端,同樣在收到`SIGTERM`信號時先停掉HTTP服務再正常退出。至此,我們的服務器程序就靠譜很多了。