自上一篇 pm2 部署介紹 后,有面試官問道不用 pm2 做進程守護,該怎么辦?
由於 NodeJs 是單線程執行的,所以主線程拋出了一個錯誤就會退出程序。線上部署當然不可能出了異常就退出了,所以需要守護進程。
node-forever
使用 forever start simple-server.js
其原理就是崩潰就重啟一個。
Monitor.prototype.start = function (restart) {
var self = this,
child;
// 非重啟則返回自身進程
if (this.running && !restart) {
process.nextTick(function () {
self.emit('error', new Error('Cannot start process that is already running.'));
});
return this;
}
// 重啟標志傳入時,重新 fork 進程,方法里使用了 child_process.spawn 執行命令來衍生一個新進程
child = this.trySpawn();
if (!child) {
process.nextTick(function () {
self.emit('error', new Error('Target script does not exist: ' + self.args[0]));
});
return this;
}
this.ctime = Date.now();
this.child = child;
this.running = true;
this.isMaster = cluster.isMaster;
process.nextTick(function () {
self.emit(restart ? 'restart' : 'start', self, self.data);
});
function onMessage(msg) {
self.emit('message', msg);
}
// Re-emit messages from the child process
this.child.on('message', onMessage);
// 監聽退出事件,崩潰時退出也算。
child.on('exit', function (code, signal) {
var spinning = Date.now() - self.ctime < self.minUptime;
child.removeListener('message', onMessage);
self.emit('exit:code', code, signal);
function letChildDie() {
self.running = false;
self.forceStop = false;
self.emit('exit', self, spinning);
}
function restartChild() {
self.forceRestart = false;
process.nextTick(function () {
self.start(true);
});
}
self.times++;
// 強制關閉,當重啟次數過多
if (self.forceStop || (self.times >= self.max && !self.forceRestart)
|| (spinning && typeof self.spinSleepTime !== 'number') && !self.forceRestart) {
letChildDie();
}
// 按照最小的重啟時間間隔,防止不停崩潰重啟
else if (spinning) {
setTimeout(restartChild, self.spinSleepTime);
}
else {
restartChild();
}
});
// 返回重啟后的新進程
return this;
};
shell 腳本啟動守護 node
實例
WEB_DIR='/var/www/ourjs'
WEB_APP='svr/ourjs.js'
#location of node you want to use
NODE_EXE=/root/local/bin/node
while true; do
{
$NODE_EXE $WEB_DIR/$WEB_APP config.magazine.js
echo "Stopped unexpected, restarting \r\n\r\n"
} 2>> $WEB_DIR/error.log
sleep 1
done
這個文件非常簡單,只有啟動的選項,守護的核心功能是由一個無限循環 while true; 來實現的,為了防止過於密集的錯誤阻塞進程,每次錯誤后間隔1秒重啟服務。錯誤日志記錄也非常簡單,直接將此進程控制台當中的錯誤輸出到error.log文件即可: 2>> $WEB_DIR/error.log 這一行, 2 代表 Error。
cluster API
Node 原生提供了 cluster,可以創建共享服務器端口的子進程。就如 EggJS 官網多進程模型的說明
+---------+ +---------+
| Worker | | Master |
+---------+ +----+----+
| uncaughtException |
+------------+ |
| | | +---------+
| <----------+ | | Worker |
| | +----+----+
| disconnect | fork a new worker |
+-------------------------> + ---------------------> |
| wait... | |
| exit | |
+-------------------------> | |
| | |
die | |
| |
| |
可以再業務代碼之上起一個 master 進程,他 fork 出多個 worker 進程來處理任務,每當一個 worker 掛了,會有事件傳回給 master,master 就能重新 fork 一份新的 worker。
那么只要 master 不掛,就能達到守護進程的目的。