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');});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-errorServer running at http://127.0.0.1:8080/c:githubscriptode-error.js:5 var ok = req.params.ok; ^TypeError: Cannot read property 'ok' of undefined at Server.<anonymous> (c:githubscriptode-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('', e, '', 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 = '' + 'Error ' + new Date().toISOString() + ' ' + req.url + '' + err.stack || err.message || 'unknow error' + '' ; 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 useNODE_EXE=/root/local/bin/nodewhile true; do { $NODE_EXE $WEB_DIR/$WEB_APP config.magazine.js echo "Stopped unexpected, restarting " } 2>> $WEB_DIR/error.log sleep 1done

錯誤日誌記錄也非常簡單,直接將此進程式控制制台當中的錯誤輸出到error.log文件即可: 2>> $WEB_DIR/error.log 這一行, 2 代表 Error。

推薦閱讀:

養花的錯誤「姿勢」以及糾正竅門
修行就是修正自己錯誤的行為,不是別人的
女人在婚姻里常犯的錯誤
揭秘:女人在床上 易犯10個錯誤【圖】

TAG:錯誤 | 辦法 | 服務 | 日誌 |