Node.js是一套用來編寫高性能網絡服務器的JavaScript工具包,一系列的變化由此開始。
也許你還不知道,JavaScript現在已經成了一門可編寫出效率極高的、可用于開發產品級web服務器的出色語言。起初我也不相信,但2009年啟動的兩個項目讓這成為可能:CommonJS和Node。
盡管JavaScript已經出現很長一段時間了,運用也很廣泛(可以說是這一星球上最常用的編程語言),但它一直只是局限在瀏覽器的范圍內。與此同時,一些框架試圖將JavaScript引入到服務器端,這些框架有Aptana Jaxer,(采用了SpiderMonkey的 JavaScript解釋器)和Helma(基于Rhino),但自身的不足又制約著它們的普及。
技術生態圈
當我們選擇某種技術編寫應用程序時,我們不只是選擇編程語言,還同時選擇了其相應的庫文件。如果一種語言有一個活躍的社區以及大量可用的庫,那么你很容易用更短的時間編寫出你的應用程序。
所有的現代編程語言都有一個標準庫以及一個優秀的第三方代碼庫組成的技術生態圈(ecosystem)。Python是一種眾所周知的以batteries included"為特色的語言,而且有一個優秀的軟件包生態圈形成的Python Package Index (PyPI),Ruby和Perl也是這樣的。不幸的是,JavaScript卻并非如此。
直到最近,你才能在沒有SpiderMonkey, V8, 或 JavaScriptCore這些JavaScript解釋器的情況下,運行服務端的JavaScript代碼。但是沒有庫的支持,你就無法多快好省地做出什么實際的東西來。
不過,在2009年JavaScript社區就意識到需要作出一些改變了。Kevin Dangoor在他的博客中說,雖然JavaScript是一種很通行的語言,但卻沒有形成標準的庫API,也沒有對外部庫進行打包和制定統一的調用方法。由于沒有通用的API,每個服務端的JavaScript項目不得不各自為政,這不利于跨項目的庫和工具形成一個更龐雜的JavaScript生態圈。
因此Dangoor啟動了ServerJS項目。其宗旨是制定一個大型的、可兼容的JavaScript生態圈所需的API。推出一周后內,ServerJS小組就有了224名成員,郵件列表里也有了653條信息。顯然,Dangoor已經引起了開發人員的注意。該項目后來改名CommonJS,以更好地反映其團結JavaScript社區、為瀏覽器端和服務器端制定統一API的這一偉大目標。
同時,也是在2009年,Ryan Dahl還啟動了一個名叫Node的JavaScript全新框架。Node又名Node.js或Nodejs,后面這兩個名稱更易于搜索。Node包含了Google的V8 解釋器,并將其與CommonJS的庫文件API捆綁起來,形成了一個可以不依賴瀏覽器而使用的完整環境。
在2009年,還有第三件關于JavaScript的事值得引得人們的注意。那就是以JavaScript為議題的會議開始出現。Chris Williams 和 Iterative Designs 創立了JSConf,這是JavaScript開發者的第一個專業會議。
突破性的演講
雖然Dahl在2009年初就啟動了Node項目,但是它真正出彩是在11月的柏林JSConf上,Dahl作了一個關于它的演講。從那以后,web開發人員對Node的關注明顯增加,其關注度還在不斷攀升。在這兩次JSConf中,Dahl是唯一一位在演說結束后享受到起立鼓掌的發言人。在滿堂的同行們面前展示最新的技術,這無疑是令人興奮不已的事情。
由于服務器上的JavaScript已經說了多年了,你可能很想知道Node究竟有什么大不了的。是什么讓它如此特別?這是因為使用Node編程,自始至終專注的是事件驅動I/O。
一般說來,實現高性能的服務器主要有以下三種編寫方式:
1. 使用多進程
2. 或者使用多線程
3. 使用單線程異步事件
Node是一個以事件為基礎的框架,并恪守非阻塞API的策略。
在 Java, C#, Perl, Python, Ruby或是 PHP這些語言中,使用多進程或多線程程序是一種更為通行、也更傳統的方式。雖然用這些語言也可以實現基于事件的編程,但卻不符合這些語言的習慣。(Twisted或Tornado是基于事件的Python框架,Ruby里則有EventMachine)
實例
雖然在其它語言中,基于事件的編程是一種不太常見的風格,但卻是編寫面向瀏覽器JavaScript代碼的首要方式,Node就沿襲了這一傳統。不論是寫面向瀏覽器的代碼還是用Node寫服務端代碼,都可以用事件的編程方式來實現。
例如,下面是jQuery的文檔中所介紹的如何發起一個異步(即Ajax)數據請求:
$.get(ajax/test.html, function(data) {
$(.result).html(data);
alert(Load was performed.);
});
在Node中編寫的基于事件的程序又是什么樣子呢?以下是Dahl在JSConf演示的代碼段:
db.query("select..", function (result) {
// use result
});
在這個例子中,完成了一個數據庫查詢,而同時也附上了一個回調函數。當數據庫返回結果時,回調函數將會被執行。代碼塊通過事件相聯系。如果沒有數據庫事件被觸發,該程序可以運行其它代碼,處理其它事件。
與之相對照,用普通的處理方式,這就得寫成這樣:
var result = db.query("select * from T");
// use result
普通的處理方式中存在的問題是:在等待數據庫返回結果時,整個程序都被阻塞了,什么事都做不了。解決這一問題的傳統辦法是把數據庫調用放到另外一個單獨的線程或進程中。而Dahl在JSConf上介紹說,這種基于事件的模型更能有效的利用CPU和內存,同時其可擴展性也更好。與多進程或者多線程程序相比,基于事件的框架可以事半功倍。
Node入門
下面是試用最新版Node最簡單的方法。
$ git clone git://github.com/ry/node.git
$ cd node
$ ./configure
$ make
$ sudo make install
$ node-repl
比較獨特的是,Node.js會假設你是在POSIX環境下運行它Linux 或 Mac OS X。如果你是在Windows下,那就需要安裝MinGW以獲得一個仿POSIX的環境。
在Node中,Http是首要的。Node為創建http服務器作了優化,所以你在網上看到的大部分示例和庫都是集中在web上(http框架、模板庫等)。
下面是一個簡單的hello worldWeb服務器:
var sys = require(sys),
http = require(http);
server = http.createServer(function (req, res) {
res.writeHeader(200, {Content-Type: text/plain});
res.write(Hello World);
res.close();
})
server.listen(8000);
sys.puts(Server running at a href="http://127.0.0.1:8000/);
讓我們分別來看這段代碼的各個部分:
var sys = require(sys),
http = require(http);
這就是CommonJS API導入的地方。require函數是導入模塊的標準方式。在CommonJS出現之前,JavaScript程序員得使用類似于Python中的import語句或是Ruby中的require一樣導入代碼包。
server = http.createServer(function (req, res) {
res.writeHeader(200, {Content-Type: text/plain});
res.write(Hello World);
res.close();
})
createServer函數需要設置一個回調函數,以便每次有新的請求到來時回調函數都會運行。
以下是如何運行該Web服務器示例:
$ node hello-world.js
Server running at a href="http://127.0.0.1:8000/
即使是只用這么少量的代碼,Node都能快速地部署一個真正的服務器。如果要測試服務器的性能,Apache Bench是一個優秀的負載測試小工具。在我的MacBook Pro(3.0 GHz Intel Core 2 Duo 以及4GB 1067 MHz 內存)上,這就是"hello world"服務器在每次4個請求、總共10,000個請求下的表現:
$ ab -c 4 -n 10000 a href="http://127.0.0.1:8000/
...
Requests per second: 6560.50 [#/sec] (mean)
...
當然,大多數的基準測試數據都不可信。上面的這個數據尤甚,因為它是在一臺筆記本電腦上運行的,而不是通過一個真正的網絡。然而,對每秒6560次請求進行處理,這并沒有什么可笑之處。V8是一個強大快速的JavaScript解釋器,Node又進一步對它進行了改進,讓它成為一個引人注目的、可用于構建服務器的平臺。
那么,它有什么缺陷嗎?
由于Node剛問世不久,Rails和Django 認為有一些特性還不完備,這是理所當然的。在開發過程中,有一個很小、但是卻很重要的特性還沒實現,那就是在服務器的源代碼改動之后自動重啟服務器。其它的一些功能,像步進調試器(step-debugger)和REPL交互提示都已經可用并且很管用,但這跟Python和Ruby中的相應工具比較起來還是稍顯粗糙。
在Node的代碼庫中正在發生許多變動,各個版本的核心API也會不同。當然,這些變化會讓API更一致,所以我也不會埋怨什么。Node的開發速度讓人想起了Ruby on Rails的初期,大量的創意付諸實施,一個星期不關注,你就會發現很多東西都改變了。這就是這個項目最好的、也是最不好的地方。更多的眼球關注會讓更多的bug得到修復,新功能也將更快地得以實現,這是好事;但壞處就是你得努力跟上它的變化。
直到不久前,用JavaScript編寫服務器軟件還是一個夢想。在Node出現之前,有許多更好,更快的技術可供選擇。但現在,情況已經改變了,Node參與到競爭中來了,并且是你下一個服務器軟件開發語言選項中的有力競爭者。
-- 學海無涯