?
被吐嘈的NodeJS的異常處理
許多人都有這樣一種映像,NodeJS比較快; 但是因為其是單線程,所以它不穩定,有點不安全,不適合處理復雜業務; 它比較適合對并發要求比較高,而且簡單的業務場景。?
在Express的作者的TJ Holowaychuk的?告別Node.js一文中列舉了以下罪狀:?
Farewell NodeJS (TJ Holowaychuk)?
??? you may get duplicate callbacks?
??? you may not get a callback at all (lost in limbo)?
??? you may get out-of-band errors?
??? emitters may get multiple “error” events?
??? missing “error” events sends everything to hell?
??? often unsure what requires “error” handlers?
??? “error” handlers are very verbose?
??? callbacks suck?
其實這幾條主要吐嘈了兩點: node.js錯誤處理很扯蛋,node.js的回調也很扯蛋。?
事實上呢?
事實上NodeJS里程確實有“脆弱”的一面,單線程的某處產生了“未處理的”異常確實會導致整個Node.JS的崩潰退出,來看個例子, 這里有一個node-error.js的文件:?
var http = require('http');var server = http.createServer(function (req, res) { //這里有個錯誤,params 是 undefined var ok = req.params.ok; res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello World\n'); }); server.listen(8080, '127.0.0.1'); console.log('Server running at http://127.0.0.1:8080/');
啟動服務,并在地址欄測試一下發現?http://127.0.0.1:8080/? 不出所料,node崩潰了?
$ node node-error
Server running at http://127.0.0.1:8080/c:\github\script\node-error.js:5 var ok = req.params.ok; ^ TypeError: Cannot read property 'ok' of undefined at Server.<anonymous> (c:\github\script\node-error.js:5:22) at Server.EventEmitter.emit (events.js:98:17) at HTTPParser.parser.onIncoming (http.js:2108:12) at HTTPParser.parserOnHeadersComplete [as onHeadersComplete] (http.js:121:23) at Socket.socket.ondata (http.js:1966:22) at TCP.onread (net.js:525:27)
怎么解決呢?
其實Node.JS發展到今天,如果連這個問題都解決不了,那估計早就沒人用了。?
使用uncaughtException
我們可以uncaughtException來全局捕獲未捕獲的Error,同時你還可以將此函數的調用棧打印出來,捕獲之后可以有效防止node進程退出,如:?
process.on('uncaughtException', function (err) {//打印出錯誤console.log(err);//打印出錯誤的調用棧方便調試 console.log(err.stack); });
這相當于在node進程內部進行守護, 但這種方法很多人都是不提倡的,說明你還不能完全掌控Node.JS的異常。?
使用 try/catch
我們還可以在回調前加try/catch,同樣確保線程的安全。?
var http = require('http');http.createServer(function(req, res) { try { handler(req, res); } catch(e) { console.log('\r\n', e, '\r\n', e.stack); try { res.end(e.stack); } catch(e) { } } }).listen(8080, '127.0.0.1'); console.log('Server running at http://127.0.0.1:8080/'); var handler = function (req, res) { //Error Popuped var name = req.params.name; res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello ' + name); };
這種方案的好處是,可以將錯誤和調用棧直接輸出到當前發生的網頁上。?
集成到框架中
標準的HTTP響應處理會經歷一系列的Middleware(HttpModule),最終到達Handler,如下圖所示:??
這 些Middleware和Handler在NodeJS中都有一個特點,他們都是回調函數,而回調函數中是唯一會讓Node在運行時崩潰的地方。根據這個 特點,我們只需要在框架中集成一處try/catch就可以相對完美地解決異常問題,而且不會影響其它用戶的請求request。?
事實上現在的NodeJS WEB框架幾乎都是這么做的,如?OurJS開源博客所基于的?WebSvr?
就有這么一處異常處理代碼:?
Line: 207? try {handler(req, res);} catch(err) {var errorMsg= '\n' ????? + 'Error ' + new Date().toISOString() + ' ' + req.url ????? + '\n' ????? + err.stack || err.message || 'unknow error' ????? + '\n' ????? ; ??? console.error(errorMsg); ??? Settings.showError ????? ? res.end('<pre>' + errorMsg + '</pre>') ????? : res.end(); ? }
那么不在回調中產生的錯誤怎么辦?不必擔心,其實這樣的node程序根本就起不起來。?
此外node自帶的?cluster?也有一定的容錯能力,它跟nginx的worker很類似,但消耗資源(內存)略大,編程也不是很方便,OurJS并沒有采用此種設計。?
守護NodeJS進程和記錄錯誤日志
現 在已經基本上解決了Node.JS因異常而崩潰的問題,不過任何平臺都不是100%可靠的,還有一些錯誤是從Node底層拋出的,有些異常 try/catch和uncaughtException都無法捕獲。之前在運行ourjs的時侯,會偶爾碰到底層拋出的文件流讀取異常,這就是一個底層 libuv的BUG,node.js在0.10.21中進行了修復。?
面對這種情況,我們就應該為nodejs應用添加守護進程,讓NodeJS遭遇異常崩潰以后能馬上復活。?
另外,還應該把這些產生的異常記錄到日志中,并讓異常永遠不再發生。?
使用node來守護node
node-forever?提供了守護的功能和LOG日志記錄功能。?
安裝非常容易?
[sudo] npm install forever
使用也很簡單?
$ forever start simple-server.js
$ forever list[0] simple-server.js [ 24597, 24596 ]
還可以看日志?
forever -o out.log -e err.log my-script.js
?
使用shell啟動腳本守護node
使用node來守護的話資源開銷可能會有點大,而且也會略顯復雜,OurJS直接在開機啟動腳本來進程線程守護。?
如在debian中放置的 ourjs 開機啟動文件:?/etc/init.d/ourjs?
這個文件非常簡單,只有啟動的選項,守護的核心功能是由一個無限循環 while true; 來實現的,為了防止過于密集的錯誤阻塞進程,每次錯誤后間隔1秒重啟服務?
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
?
錯誤日志記錄也非常簡單,直接將此進程控制臺當中的錯誤輸出到error.log文件即可: 2>> $WEB_DIR/error.log? 這一行, 2 代表 Error。
?
轉自:?OurJS