1.異常處理
在我們進行代碼開發的時候,異常的捕獲處理是一個不能忽略的話題,那么怎么才能捕獲到node中的異常呢?或許你最先想到的是try/catch的使用,如下例:
var http = require('http') var opts = { host: 'sfnsdkfjdsnk.com', port: 80, path: '/' } try { http.get(opts, function(res) { console.log('Will this get called?') }) } catch (e) { console.log('Will we catch an error?') }
在回答上面代碼是否能完成任務之前,我們先做個分析。node采用的是非堵塞的I/O操作,在上面的代碼中我調用http.get(),向他傳入了一個回調函數,當I/O操作完成的時候回調函數將會被觸發。然而,http.get()觸發一個回調函數之后就會成功的返回,在執行GET操作過程中的異常是不能被try/catch捕獲到的。因為在node中產生了異常向外報告的時候,我們已經離開了javascript的調用堆棧,javascript已經去處理別的事件去了。說的簡單點,上面的try/catch是為http.get()准備的,不是為他里面的function(res){}回調函數准備的。然而http.get()是不會發生異常的,他只負責觸發function(res){}回調函數,但是回調函數的執行域已經超出了try/catch。
那么有辦法捕獲到回調函數里面的異常嗎?答案是使用異常事件。
var http = require('http') var opts = { host: 'dskjvnfskcsjsdkcds.net', port: 80, path: '/' } var req = http.get(opts, function(res) { console.log('This will never get called') }) req.on('error', function(e) { console.log('Got that pesky error trapped') })
2.多進程開發
在前面的課程中,我們已經了解到node是單線程的,他只使用一個cpu去工作。但是現在多核的處理器那么多,合理的利用多個cpu工作可以大大提高程序的效率,要是不能有效利用服務器資源那不是很可惜。那有沒有辦法讓node也利用服務器上的多個cpu呢,答案是可以使用集群(cluster)技術把多個工作委托給多個子進程去做。node會在其他進程創建當前程序的拷貝。每一個子進程都有些特殊的能力,比如可以和其他進程共享一個socket連接。在使用集群技術的時候,需要創建一個主進程和多個子進程,主進程不負責對具體請求的處理,他只負責工作任務的分配,具體的工作都是交給子進程去處理的。
讓我們來看一個例子:
var cluster = require('cluster'); var http = require('http'); var numCPUs = require('os').cpus().length; if (cluster.isMaster) { // Fork workers. for (var i = 0; i < numCPUs; i++) { cluster.fork(); } cluster.on('death', function(worker) { console.log('worker ' + worker.pid + ' died');
cluster.fork(); }); } else { // Worker processes have a http server. http.Server(function(req, res) { res.writeHead(200); res.end("hello world\n"); }).listen(8000); }
通過上面代碼,我們使用os模塊獲取cpu的個數,使用cluster模塊創建一個master進程,根據cpu的個數使用cluster.fork()創建相應個數的工作進程,工作進程共享同一個http服務。一個工作進程的錯誤,不會影響其他工作進程的正常工作。我們使用cluster.on('death', function(worker){},檢測工作進程的消亡,一個工作進程消亡以后創建一個全新的工作進程。
在學會使用cluster創建主進程和工作進程之后,我們來學習如何實現他們之間的通信。看下例:
var cluster = require('cluster'); var http = require('http'); var numCPUs = require('os').cpus().length; var rssWarn = (12 * 1024 * 1024), heapWarn = (10 * 1024 * 1024); if(cluster.isMaster) { for(var i=0; i<numCPUs; i++) { var worker = cluster.fork(); worker.on('message', function(m) { if (m.memory) { if(m.memory.rss > rssWarn) { console.log('Worker ' + m.process + ' using too much memory.') } } }) } } else { //Server http.Server(function(req,res) { res.writeHead(200); res.end('hello world\n') }).listen(8000)
//Report stats once a second setInterval(function report(){ process.send({memory: process.memoryUsage(), process: process.pid}); }, 1000) }
主進程中,使用worker.on('message', function(m) {...})來監聽來自工作進程傳遞的信息。工作進程使用setInterval函數間隔性的調用process.send(),向主進程匯報之間的情況。
如果一些工作進程耗時太長,或者已經沒有響應,我們如何關閉它呢,來看下例:
var cluster = require('cluster'); var http = require('http'); var numCPUs = require('os').cpus().length; var rssWarn = (50 * 1024 * 1024), heapWarn = (50 * 1024 * 1024); var workers = {} if(cluster.isMaster) { for(var i=0; i<numCPUs; i++) { createWorker() } setInterval(function() { var time = new Date().getTime() for(pid in workers) { if(workers.hasOwnProperty(pid) && workers[pid].lastCb + 5000 < time) { console.log('Long running worker ' + pid + ' killed') workers[pid].worker.kill() delete workers[pid] createWorker() } } }, 1000) } else { //Server http.Server(function(req,res) { //mess up 1 in 200 reqs if (Math.floor(Math.random() * 200) === 4) { console.log('Stopped ' + process.pid + ' from ever finishing') while(true) { continue } } res.writeHead(200); res.end('hello world from ' + process.pid + '\n') }).listen(8000)
//Report stats once a second setInterval(function report(){ process.send({cmd: "reportMem", memory: process.memoryUsage(), process: process.pid}) }, 1000) }
function createWorker() { var worker = cluster.fork() console.log('Created worker: ' + worker.pid) //allow boot time workers[worker.pid] = {worker:worker, lastCb: new Date().getTime()-1000} worker.on('message', function(m) { if(m.cmd === "reportMem") { workers[m.process].lastCb = new Date().getTime() if(m.memory.rss > rssWarn) { console.log('Worker ' + m.process + ' using too much memory.') } } }) }
答案就是需要我們在主線程中去關閉它。
上面的例子中,我們故意用下面的代碼
if (Math.floor(Math.random() * 200) === 4) { console.log('Stopped ' + process.pid + ' from ever finishing') while(true) { continue } }
讓程序有機會堵塞工作進程,這樣主進程就有機會得到一個超時的工作進程,從而關閉它了。