Nodejs設計的核心理念:1.事件迴圈;2.模式;3.差錯處理;4.運用多處理器 ...
最近看《node即學即用》,做點筆記~~
Nodejs設計的核心理念:
1.事件迴圈:
要點一:所有I/O事件都是非阻塞的
要點二:用回調函數來處理I/O,回調函數以級聯的方式嵌在其他回調函數中
要點三:事件驅動是以“單線程”的方式運行,即同一時間只能處理一件事情
例子:web伺服器要求從資料庫讀取一些數據,然後返回給用戶
1.web伺服器請求-->回調函數A【從請求對象中得知要從資料庫讀些什麼】
2.回調函數A-->資料庫【發起具體請求,並傳入回調函數B】
3.回調函數A結束並返回
4.資料庫【找到所需要的內容,觸發相應事件】-->回調函數B【由事件迴圈隊列調用】
5.回調函數B【將數據發給用戶】
不適用node:伺服器大部分工作是計算
編寫node伺服器策略:
1.設置完成後,所有操作都是事件驅動的
2.如果Node.js需要長時間處理數據,就需要考慮把它分配給web worker去處理
**事件驅動方法配合事件迴圈工作起來非常高效-->但是如果所有的回調函數都用匿名函數
1.無法控制代碼在哪裡使用【匿名函數只在被使用的地方纔存活,而不是在綁定事件回調時存活】
2.會影響調試【異常發生時,很難分辨出是那個回調函數導致了問題】
2.模式
I/O模式有三種:無序的並行I/O、順序串列I/O、有序的並行I/O
【無序的並行I/O】操作I/O時,只要扔出請求然後等待結果就好了-->無序不是亂序,而是順序無法保證
例子:
1 fs.readFile('foo.txt','utf8',function(err,data){ 2 console.log(data); 3 }); 4 fs.readFile('foo.txt','utf8',function(err,data){ 5 console.log(data); 6 });
【順序串列I/O】每個任務必須在上一個任務完成後才能開始-->嵌套回調方法
例子:
1 server.on('request',function(req,res){ 2 //從memcached里獲取session信息 3 memcached.getSession(req,function(session){ 4 //從db獲取信息 5 db.get(session.user,function(userData){ 6 //其他Web服務調用 7 ws.get(req,function(wsData){ 8 //渲染頁面 9 page=pageRender(req,session,userData,wsData); 10 //輸出響應內容 11 res.write(page); 12 }); 13 }); 14 }); 15 });
**以上例子可讀性較差,可以用以下幾種方法修改
【回調函數中的命名函數】
1 server.on('request',getMemCached(req,res){ 2 memcached.getSession(req,getDbInfo(session){ 3 db.get(session.user,getWsInfo(userData){ 4 ws.get(req,render(wsData){ 5 //渲染頁面 6 page=pageRender(req,session,userData,wsData); 7 //輸出響應內容 8 res.write(page); 9 }); 10 }); 11 }); 12 });
【用聲明函數把代碼分離】**當狀態不需要在3個以上回調函數中共用時比較合適
1 var render=function(wsData){ 2 page=pageRender(req,session,userData,wsData); 3 }; 4 5 var getWsInfo=function(userData){ 6 ws.get(req,render); 7 }; 8 9 var getDbInfo=function(session){ 10 db.get(session.user,getWsInfo); 11 }; 12 13 var getMemCached=function(req,res){ 14 memcached.getSession(req,getDbInfo); 15 };
**讓數據再函數間傳遞有許多方法-->一般使用javascript語言本身的特性
1.javascript有函數作用域,函數內定義的變數僅函數內可見
2.內嵌的回調函數中可以訪問外部回調函數內定義的變數,即使外部函數已經返回並關閉了也依舊可以訪問
3.當嵌套回調函數時,我們隱式地把所有之前回調函數中的變數都綁定到最新定義的回調函數內
--->仍然用展開的重構方法,再創建一個原始請求都包含的共用作用域,用一個閉包把所有回調函數都包含進去
--->這樣,所有的初始請求相關的回調函數都被封裝起來,並通過閉包內的變數共用狀態
【在回調函數中封裝】
1 server.on('request',function(req,res){ 2 var render=function(wsData){ 3 page=pageRender(req,session,userData,wsData); 4 }; 5 6 var getWsInfo=function(userData){ 7 ws.get(req,render); 8 }; 9 10 var getDbInfo=function(session){ 11 db.get(session.user,getWsInfo); 12 }; 13 14 var getMemCached=function(req,res){ 15 memcached.getSession(req,getDbInfo); 16 }; 17 18 });
【中間件方式】如:Connect/Express框架中的模塊
**由於javascript中對象是以引用的方式傳遞的,修改對象的值會修改所有該對象的引用,會有很大的風險
-->方法:所有需要依賴某狀態的函數通過統一的介面來互相傳遞
-->故中間件的形式:function(req,res,next)
3.差錯處理
**不能用javascript的try/catch來處理
-->因為當回調函數調用時,程式早已經不在原來那個棧了,而try/catch只有當錯誤發生在內聯位置才有用
**用error時間來捕捉錯誤並處理
例子:
1 var http=require('http'); 2 3 var opts={ 4 host:'dskjakjglaljlelg.net', 5 port:80, 6 path:'/' 7 }; 8 9 var req=http.get(opts,function(res){ 10 console.log('This will never get called'); 11 }); 12 13 req.on('error',function(e){ 14 console.log('got that pesky error trapped'); 15 });
4.使用多處理器
**cluster模塊可以將任務分配給子線程
-->共用程式段給子線程後,主線程不會參與到具體事務中,請求直接連接到子線程
例子:【使用集群來分發任務】
1 var cluster=require('cluster'); 2 var http=require('http'); 3 var numCPUs=require('os').cpus().length; 4 5 if(cluster.isMaster){ 6 //創建工作進程 7 for(var i=0;i<numCPUs;i++){ 8 cluster.fork(); 9 } 10 11 cluster.on('death',function(worker){ 12 console.log('worker '+worker.pid+' died'); 13 }); 14 }else{ 15 //工作進程創建http伺服器 16 http.Server(function(req,res){ 17 res.writeHead(200); 18 res.end("hello world!\n"); 19 }).listen(8000); 20 }
**cluster.isMaster 和 cluster.isWorker
1.主進程中,cluster.isMaster為true;cluster.isWorker為false;
2.子進程中,cluster.isMaster為false;cluster.isWorker為true;
**cluster提供了跨平臺讓多個進程共用socket的方法,即多個子進程共用一個埠上的連接
-->上例中為工作進程創建http伺服器並設置監聽
**cluster還可以判斷子進程的健康狀態
-->上例中 cluster.on('death',function(worker){...};
**cluster還可以監控工作進程狀態,包括記憶體使用量(用memory屬性)
**cluster還可以通過監控子進程多長時間沒有變化,找出僵屍進程,並將其殺死