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