做者:王 羣鋒, 軟件工程師, IBMhtml
出處:http://www.ibm.com/developerworks/cn/web/1201_wangqf_nodejs/java
Node.js 被設計用來開發大規模高併發的網絡應用,這種網絡應用的瓶頸之一是在 I/O 的處理效率上。因爲硬件及網絡的限制,I/O 的速度每每是固定的,如何在此前提下儘量處理更多的客戶請求,提升 CPU 使用效率,便成了開發人員面臨的最大問題。得益於基於事件驅動的編程模型,Node.js 使用單一的 Event loop 線程處理客戶請求,將 I/O 操做分派至各異步處理模塊,既解決了單線程模式下 I/O 阻塞的問題,又避免了多線程模式下資源分配及搶佔的問題。至於使用 JavaScript 開發服務器端代碼,這並非什麼新鮮事物,JavaScript 原本就是一種完備的編程語言,微軟的 IIS 服務器很早就支持 JavaScript 在其中運行。本文將重點講述 Node.js 基於事件的編程模型,並與傳統的處理方式進行對比,幫助您更好的理解 Node.js。node
網絡應用的性能瓶頸程序員
網絡應用的性能瓶頸之一在於 I/O 處理上,下表來自 Node.js 的做者 Ryan Dahl 爲 JSConf 大會所做的 講演,對比了在不一樣介質上進行 I/O 操做所花費的 CPU 時間。您可以清楚的發現,訪問磁盤及網絡數據所花費的 CPU 時間是訪問內存時的數十萬倍,而如今的網絡應用,卻須要大量的訪問磁盤及網絡,好比數據庫查詢、訪問互聯網等。如何提升此時 CPU 的利用效率,便成了提高網絡應用性能的關鍵。web
I/O | CPU Cycle |
---|---|
L1-cache | 3 |
L2-cache | 14 |
RAM | 250 |
Disk | 41000000 |
Network | 240000000 |
回頁首ajax
傳統的處理方式數據庫
單線程express
var result = db.query("select * from T"); // 使用該查詢結果 |
上述代碼描述了一個常見的案例,客戶端發起一個 I/O 請求,而後等待服務器端返回 I/O 結果,結果返回後再對其進行操做,但這種請求經常須要很長時間(對於服務器的 CPU 處理能力來講)。這一過程當中,服務器沒法接受新的請求,即阻塞式 I/O。這種處理方式雖然簡單,卻不實用,尤爲是面對大量請求的時候,簡直就不可用。這種情景相似在火車站售票窗口排隊買票,若是您在春節期間去北京火車站排隊買過票,毫不會認爲這是一種好的處理方式。慶幸的是,如今不多有服務器採起這種處理方式。編程
多線程服務器
var result = db.query("select * from T"); // 使用該查詢結果 |
該方式下,服務器爲每一個請求分配一個線程,全部任務均在該線程內執行,就像火車站多開了幾個賣票窗口,處理效率高了許多。但就如讀者看到的那樣,在春節期間各個售票窗口前仍是人滿爲患,爲何火車站再也不多開一些售票窗口呢?固然是由於成本。線程也同樣,服務器每建立一個線程,每一個線程大概會佔用 2M 的系統內存,並且線程之間的切換也會下降服務器的處理效率,基於成本的考慮,這種處理方式也有必定的侷限性。然而,這卻不是最主要的,主要的是開發多線程程序很是困難,容易出錯。程序員需考慮死鎖,數據不一致等問題,多線程的程序極難調試和測試。基本上在程序運行出錯的時候,程序員才知道本身的程序有錯誤。而這種錯誤的代價每每又是巨大的,那些訪問量巨大的電子商務網站時常會曝出價格錯誤等致使公司損失的新聞。
db.query("select..", function (result) { // 使用該查詢結果 }); // 繼續幹其餘的事 // …… |
上述代碼的好處是:使用一個線程執行,客戶發起 I/O 請求的同時傳入一個函數,該函數會在 I/O 結果返回後被自動調用,並且該請求不會阻塞後續操做。就像電話訂票,設想你一大早來到辦公室,給火車站打個電話,將本身的票務信息,地址告訴對方,而後放下電話,泡杯茶,瀏覽一下網頁,回覆一下今天的電子郵件,你徹底不用管火車票的事了,若是訂到票,火車站會派快遞公司按你電話中提到的聯繫方式送票給你。無疑,這是一種極其理想的處理方式。
下圖說明了這種編程模型,全部請求以及同時傳入的回調函數均發送至同一線程,該線程一般叫作 Event loop 線程,該線程負責在 I/O 執行完畢後,將結果返回給回調函數。這裏要注意的是 I/O 操做自己並不在該線程內執行,因此不會阻塞後續請求。
有了上面對於事件處理編程模型的介紹,Node.js 就很好理解了。Node.js 是採用事件處理編程模型的 JavaScript 平臺,它容許程序員開發大規模高併發的網絡應用。這個概念並不新鮮,在 Node.js 以前,不少語言都提供了相似的平臺:Python 的 Twisted,Perl 的 AnyEvent,Ruby 的 EventMachine。Node.js 優於其餘平臺的另外一個好處是全部的 I/O 操做都以異步方式實現,讓程序員將主要精力放在應用的業務邏輯上。
事實上,在實現 Node.js 之初,做者 Ryan Dahl 並無選擇 JavaScript,他嘗試過 C、Lua,皆因其欠缺一些高級語言的特性,如閉包、函數式編程,導致程序複雜,難以維護。而 JavaScript 則是支持函數式編程範型的語言,很好地契合了 Node.js 基於事件驅動的編程模型。加之 Google 提供的 V8 引擎,使 JavaScript 語言的執行速度大大提升。最終呈如今咱們面前的就成了 Node.js,而不是 Node.c,Node.lua 或其餘語言的實現。
本文將在這裏使用 Node.js 實現一個小型的 Web 應用,它將隨機爲用戶顯示一條諺語或名人名言,並容許瀏覽者添加本身喜歡的諺語。用 Node.js 開發 Web 應用很是簡單,下面這段一百多行的代碼就實現了一個完整的應用。若是您還沒有安裝好 Node.js,請登陸其官方網站查看詳細安裝說明。
// 導入所需模塊 var http = require("http"); var url = require("url"); var qs = require('querystring'); |
首先須要導入該應用所須要的模塊,其中 http 模塊負責建立 Web 服務器及 HTTP 相關服務,url 模塊負責解析 URL 地址,querystring 模塊負責處理請求參數。
// 這裏爲了方便使用了全局變量 var proverbs = [ "The turtle wins the race.", "God hides in the details.", "There are two ways to write error-free programs; only the third one works.", "Perfect practice makes perfect." ]; |
這裏爲了方便,使用全局變量 proverbs
存儲已有諺語,在正式的應用中,應該考慮使用文件或數據庫存儲。
// 建立一個 Web 服務器 http.createServer(onRequest).listen(8888); console.log("server is running..."); |
使用 Node.js 開發 Web 應用很是簡單,甚至不用配置 Web 服務器,一行代碼就建立成功一個 Web 服務器,同時傳入一個回調函數,服務器建立成功後,代碼並無阻塞到那裏,而是接着往下執行,這就是事件驅動模型的編程風格,在 Node.js 裏將會大量採用這種方式。
// 請求處理函數 function onRequest(request, response) { var pathname = url.parse(request.url).pathname; console.log("Reqeust for " + pathname + " received."); if (pathname === "/" || pathname === "/index" || pathname === "/proverb") { getProverb(response); } else if (pathname === "/add") { if (request.method.toLowerCase() == 'post') { var body = ''; request.on('data', function(data) { body += data; }); request.on('end', function() { var POST = qs.parse(body); add(POST.text, response); }); } else { addProverb(response); } } else { response.writeHead(404, { "Content-Type" : "text/plain" }); response.write("404 Not found"); response.end(); } } |
該函數負責分發請求,將接收到的 URL 根據規則轉發至對應的請求處理模塊。
function getProverb(response) { var body = '<html>' + '<head>' + '<meta http-equiv="Content-Type" content="text/html; ' + 'charset=UTF-8" />' + '</head>' + '<body style="font-size: 4em;line-height: 1.2; margin-top: 200;">' + '<blockquote>'+ proverbs[Math.floor(Math.random()* proverbs.length)] + '</blockquote>' + '</body>' + '</html>'; response.writeHead(200, { "Content-Type" : "text/html" }); response.write(body); response.end(); } |
該函數負責處理 GET 請求,隨機向用戶返回一條諺語。細心的讀者可能會發現該函數將 HTML,CSS 以及數據混在一塊兒,顯然不符合 MVC 的編程模式。Node.js 有不少第三方開發的模塊,其中 express就是一款優秀的 Web 開發框架,有興趣的讀者能夠研究一下。
function addProverb(response) { var body = '<html>' + '<head>' + '<meta http-equiv="Content-Type" content="text/html; ' + 'charset=UTF-8" />' + '</head>' + '<body style="font-size: 4em;line-height: 1.2; margin-top: 200;">' + '<form action="/add" method="post">' + '<textarea name="text" rows="10" cols="60"></textarea><p>' + '<input type="submit" value="Submit" />' + '</form>' + '</body>' + '</html>'; response.writeHead(200, { "Content-Type" : "text/html" }); response.write(body); response.end(); } |
該函數返回一個 HTML 表單,容許用戶輸入本身喜歡的諺語或格言。
function add(proverb, response) { proverbs.push(proverb); var body = '<html>' + '<head>' + '<meta http-equiv="Content-Type" content="text/html; ' + 'charset=UTF-8" />' + '</head>' + '<body style="font-size: 4em;line-height: 1.2; margin-top: 200;">' + '<blockquote>' + proverb + '</blockquote>' + '</body>' + '</html>'; response.writeHead(200, { "Content-Type" : "text/html" }); response.write(body); response.end(); } |
該函數負責用戶的 POST 請求,將用戶輸入保存到服務器端,並返回給用戶結果。
本文給你們介紹了基於事件的編程模型,這種編程模型正是 Node.js 這項最近流行技術的核心,但願讀者能利用 Node.js 的優點,爲本身的開發工做帶來便利。
學習
討論