Node.js入門:事件機制

Evented I/O for V8 JavaScripthtml

    基於V8引擎實現的事件驅動IO。
 

事件機制的實現前端

    Node.js中大部分的模塊,都繼承自Event模塊(http://nodejs.org/docs/latest/api/events.html )。Event模塊(events.EventEmitter)是一個簡單的事件監聽器模式的實現。具備addListener/on,once,removeListener,removeAllListeners,emit等基本的事件監聽模式的方法實現。它與前端DOM樹上的事件並不相同,由於它不存在冒泡,逐層捕獲等屬於DOM的事件行爲,也沒有preventDefault()、stopPropagation()、stopImmediatePropagation() 等處理事件傳遞的方法。
    從另外一個角度來看,事件偵聽器模式也是一種事件鉤子(hook)的機制,利用事件鉤子導出內部數據或狀態給外部調用者。Node.js中的不少對象,大多具備黑盒的特色,功能點較少,若是不經過事件鉤子的形式,對象運行期間的中間值或內部狀態,是咱們沒法獲取到的。這種經過事件鉤子的方式,可使編程者不用關注組件是如何啓動和執行的,只需關注在須要的事件點上便可。
 1 var options = { 
 2     host: 'www.google.com', 
 3     port: 80, 
 4     path: '/upload', method: 'POST'
 5 }; 
 6 var req = http.request(options, function (res) { 
 7     console.log('STATUS: ' + res.statusCode); 
 8     console.log('HEADERS: ' + JSON.stringify(res.headers)); 
 9     res.setEncoding('utf8'); 
10     res.on('data', function (chunk) { 
11         console.log('BODY: ' + chunk); 
12     }); 
13 }); 
14 req.on('error', function (e) { 
15     console.log('problem with request: ' + e.message); 
16 }); 
17 // write data to request body 
18 req.write('data\n'); 
19 req.write('data\n'); 
20 req.end();

    在這段HTTP request的代碼中,程序員只須要將視線放在error,data這些業務事件點便可,至於內部的流程如何,無需過於關注。node

    值得一提的是若是對一個事件添加了超過10個偵聽器,將會獲得一條警告,這一處設計與Node.js自身單線程運行有關,設計者認爲偵聽器太多,可能致使內存泄漏,因此存在這樣一個警告。能夠將這個限制去掉,調用:
emitter.setMaxListeners(0);
    其次,爲了提高Node.js的程序的健壯性,EventEmitter對象對error事件進行了特殊對待。若是運行期間的錯誤觸發了error事件。EventEmitter會檢查是否有對error事件添加過偵聽器,若是添加了,這個錯誤將會交由該偵聽器處理,不然,這個錯誤將會做爲異常拋出。若是外部沒有捕獲這個異常,將會引發線程的退出。
 

繼承event.EventEmittergit

    實現一個繼承了EventEmitter類是十分簡單的,如下是Node.js中流對象繼承EventEmitter的例子:
1 function Stream() { 
2     events.EventEmitter.call(this); 
3 }
4 util.inherits(Stream, events.EventEmitter);

    Node.js在工具模塊中封裝了繼承的方法,因此此處能夠很便利地調用。程序員能夠經過這樣的方式輕鬆繼承EventEmitter對象,利用事件機制,能夠幫助你解決一些問題。程序員

 

多事件之間協做github

    在略微大一點的應用中,數據與Web服務器之間的分離是必然的,如新浪微博、Facebook、Twitter等。這樣的優點在於數據源統一,而且能夠爲相同數據源制定各類豐富的客戶端程序。以Web應用爲例,在渲染一張頁面的時候,一般須要從多個數據源拉取數據,並最終渲染至客戶端。Node.js在這種場景中能夠很天然很方便的同時並行發起對多個數據源的請求。
1 api.getUser("username", function (profile) { 
2     // Got the profile 
3 }); 
4 api.getTimeline("username", function (timeline) { 
5     // Got the timeline 
6 }); 
7 api.getSkin("username", function (skin) { 
8     // Got the skin 
9 });

    Node.js經過異步機制使請求之間無阻塞,達到並行請求的目的,有效的調用下層資源。可是,這個場景中的問題是對於多個事件響應結果的協調並不是被Node.js原生優雅地支持。爲了達到三個請求都獲得結果後才進行下一個步驟,程序也許會被變成如下狀況:數據庫

1 api.getUser("username", function (profile) { 
2     api.getTimeline("username", function (timeline) { 
3         api.getSkin("username", function (skin) { 
4             // TODO 
5         }); 
6     }); 
7 });

    這將致使請求變爲串行進行,沒法最大化利用底層的API服務器。編程

    爲解決這類問題,有一個模塊(EventProxy,https://github.com/JacksonTian/eventproxy)來實現多事件協做,如下爲上面代碼的改進版:
 1 var proxy = new EventProxy(); 
 2 proxy.all("profile", "timeline", "skin", function (profile, timeline, skin) { 
 3    // TODO 
 4 }); 
 5 api.getUser("username", function (profile) { 
 6     proxy.emit("profile", profile); 
 7 }); 
 8 api.getTimeline("username", function (timeline) { 
 9     proxy.emit("timeline", timeline); 
10 }); 
11 api.getSkin("username", function (skin) { 
12     proxy.emit("skin", skin); 
13 });

    EventProxy也是一個簡單的事件偵聽者模式的實現,因爲底層實現跟Node.js的EventEmitter不一樣,沒法合併進Node.js中。可是卻提供了比EventEmitter更強大的功能,且API保持與EventEmitter一致,與Node.js的思路保持契合,並能夠適用在前端中。api

    這裏的all方法是指偵聽完profile、timeline、skin三個方法後,執行回調函數,並將偵聽接收到的數據傳入。
 

利用事件隊列解決雪崩問題緩存

    所謂雪崩問題,是在緩存失效的情景下,大併發高訪問量同時涌入數據庫中查詢,數據庫沒法同時承受如此大的查詢請求,進而往前影響到網站總體響應緩慢。那麼在Node.js中如何應付這種情景呢。
1 var select = function (callback) { 
2     db.select("SQL", function (results) { 
3         callback(results); 
4     }); 
5 };

    以上是一句數據庫查詢的調用,若是站點恰好啓動,這時候緩存中是不存在數據的,而若是訪問量巨大,同一句SQL會被髮送到數據庫中反覆查詢,影響到服務的總體性能。一個改進是添加一個狀態鎖。

 1 var status = "ready"; 
 2 var select = function (callback) { 
 3     if (status === "ready") { 
 4         status = "pending"; 
 5         db.select("SQL", function (results) { 
 6             callback(results); 
 7             status = "ready"; 
 8         }); 
 9     } 
10 };

    可是這種情景,連續的屢次調用select,只有第一次調用是生效的,後續的select是沒有數據服務的。因此這個時候引入事件隊列吧:

 1 var proxy = new EventProxy(); 
 2 var status = "ready"; 
 3 var select = function (callback) { 
 4     proxy.once("selected", callback); 
 5     if (status === "ready") { 
 6         status = "pending"; 
 7         db.select("SQL", function (results) { 
 8             proxy.emit("selected", results); 
 9             status = "ready";
10         }); 
11      } 
12 };

    這裏利用了EventProxy對象的once方法,將全部請求的回調都壓入事件隊列中,並利用其執行一次就會將監視器移除的特色,保證每個回調只會被執行一次。對於相同的SQL語句,保證在同一個查詢開始到結束的時間中永遠只有一次,在這查詢期間到來的調用,只需在隊列中等待數據就緒便可,節省了重複的數據庫調用開銷。因爲Node.js單線程執行的緣由,此處無需擔憂狀態問題。這種方式其實也能夠應用到其餘遠程調用的場景中,即便外部沒有緩存策略,也能有效節省重複開銷。此處也能夠用EventEmitter替代EventProxy,不過可能存在偵聽器過多,引起警告,須要調用setMaxListeners(0)移除掉警告,或者設更大的警告閥值。

相關文章
相關標籤/搜索