前面的話 幾年前,對於學習NodeJS可能還有所遲疑,怕分散了前端學習的精力。但到了現在,如果不學習nodeJS,前端的學習卻可能無法再有所進展。技術的進步就是這麼殘酷。對新技術觀望的時候,該技術已經大行其道了。本文將介紹nodeJS的基礎知識 語言選擇 Ryan Dahl是一名資深的C/C++程式 ...
前面的話
幾年前,對於學習NodeJS可能還有所遲疑,怕分散了前端學習的精力。但到了現在,如果不學習nodeJS,前端的學習卻可能無法再有所進展。技術的進步就是這麼殘酷。對新技術觀望的時候,該技術已經大行其道了。本文將介紹nodeJS的基礎知識
語言選擇
Ryan Dahl是一名資深的C/C++程式員,在創造出Node之前,他的主要工作都是圍繞高性能 Web伺服器進行的。經歷過一些嘗試和失敗之後,他找到了設計高性能,Web伺服器的幾個要點: 事件驅動、非阻塞I/O,而這也正是nodejs的兩大特點
所以Ryan Dahl最初的目標是寫一個基於事件驅動、非阻塞I/O的Web伺服器,以達到更高的性能,提供Apache等伺服器之外的選擇。寫Node的時候,Ryan Dahl曾經評估過C、Lua、Haskell、 Ruby等語言作為備選實現,結論為:C的開發門檻高,可以預見不會有太多的開發者能將它用於日常的業務開發,所以捨棄它;Ryan Dahl覺得自己還不足夠玩轉Haskell,所以捨棄它;Lua自身已經含有很多阻塞I/O庫,為其構建非阻塞I/O庫也不能改變人們繼續使用阻塞I/O庫的習慣,所以也捨棄它;而Ruby的虛擬機由於性能不好而落選
相比之下,JavaScript比C的開發門檻要低,比Lua的歷史包袱要少。儘管伺服器端JavaScript存在已經很多年了,但是後端部分一直沒有市場,可以說歷史包袱為零,為其導入非阻塞I/O庫沒有額外阻力。另外,JavaScript在瀏覽器中有廣泛的事件驅動方面的應用,暗合Ryan Dahl喜好基於事件驅動的需求。當時,第二次瀏覽器大戰也漸漸分出高下,Chrome瀏覽器的JavaScript引擎V8摘得性能第一的桂冠。考慮到高性能、符合事件驅動、沒有歷史包袱這3個主要原因,JavaScript成為了Node的實現語言
起名
起初,Ryan Dahl稱他的項目為web.js,就是一個Web伺服器,但是項目的發展超過了他最初單純開發一個Web伺服器的想法,變成了構建網路應用的一個基礎框架,這樣可以在它的基礎上構建更多的東西,諸如伺服器、客戶端、命令行工具等。Node發展為一個強制不共用任何資源的單線程、單進程系統,包含十分適宜網路的庫,為構建大型分散式應用程式提供基礎設施,其目標也是成為一個構建快速、可伸縮的網路應用平臺。它自身非常簡單,通過通信協議來組織許多Node,非常容易通過擴展來達成構建大型網路應用的目的。每一個Node進程都構成這個網路應用中的一個節點,這是它名字所含意義的真諦
特點
作為後端JavaScript的運行平臺,Node保留了前端瀏覽器JavaScript中那些熟悉的介面,沒有改寫語言本身的任何特性,依舊基於作用域和原型鏈,區別在於它將前端中廣泛運用的思想遷移到了伺服器端。Node相較於其他語言的特點如下所示
1、非同步I/O
在Node中,絕大多數的操作都以非同步的方式進行調用。Ryan Dahl排除萬難,在底層構建了很多非同步I/O的API,從文件讀取到網路請求等,均是如此。這樣的意義在於,在Node中,我們可 以從語言層面很自然地進行並行I/O操作。每個調用之間無須等待之前的I/O調用結束。在編程模型上可以極大提升效率
以同時執行兩個文件讀取任務為例,非同步I/O取決於最慢的那個文件讀取的耗時,而同步I/O的耗時是兩個任務的耗時之和。這裡非同步帶來的優勢是顯而易見的
2、事件
隨著Web 2.0時代的到來,JavaScript在前端擔任了更多的職責,事件也得到了廣泛的應用。 Node不像Rhino那樣受Java的影響很大,而是將前端瀏覽器中應用廣泛且成熟的事件引入後端, 配合非同步I/O,將事件點暴露給業務邏輯
事件的編程方式具有輕量級、松櫚合、只關註事務點等優勢,但是在多個非同步任務的場景下,事件與事件之間各自獨立,如何協作是一個問題
3、回調函數
與其他的Web後端編程語言相比,Node除了非同步和事件外,回調函數是一大特色。縱觀下來,回調函數也是最好的接受非同步調用返回數據的方式。但是這種編程方式對於很多習慣同步思路編程的人來說,也許是十分不習慣的。代碼的編寫順序與執行順序並無關係,這對他們可能造成閱讀上的障礙。在流程式控制制方面,因為穿插了非同步方法和回調函數,與常規的同步方式相比,變得不那麼一目瞭然了
4、單線程
JavaScript語言的一大特點就是單線程,也就是說,同一個時間只能做一件事。JavaScript的單線程,與它的用途有關。作為瀏覽器腳本語言,JavaScript的主要用途是與用戶互動,以及操作DOM。這決定了它只能是單線程,否則會帶來很複雜的同步問題。比如,假定JavaScript同時有兩個線程,一個線程在某個DOM節點上添加內容,另一個線程刪除了這個節點,這時瀏覽器應該以哪個線程為準?所以,為了避免複雜性,從一誕生,JavaScript就是單線程,這已經成了這門語言的核心特征
Node保持了JavaScript在瀏覽器中單線程的特點。而且在Node中,JavaScript與其餘線程是無法共用任何狀態的。單線程的最大好處是不用像多線程編程那樣處處在意狀態的同步問題,這裡沒有死鎖的存在,也沒有線程上下文交換所帶來的性能上的開銷
同樣,單線程也有它自身的弱點,具體有以下3方面:無法利用多核CPU;錯誤會引起整個應用退出,應用的健壯性值得考驗;大量計算占用CPU導致無法繼續調用非同步I/O
像瀏覽器中JavaScript與UI共用一個線程一樣,JavaScript長時間執行會導致UI的渲染和響應被中斷。在Node中,長時間的CPU占用也會導致後續的非同步I/O發不出調用,已完成的非同步I/O的回調函數也會得不到及時執行
HTML5定製了Web Workers的標準,Web Workers能夠創建工作線程來進行計算,以解決JavaScript大計算阻塞UI渲染的問題。工作線程為了不阻塞主線程,通過消息傳遞的方式來傳遞運行結果,這也使得工作線程不能訪問到主線程中的UI
Node採用了與Web Workers相同的思路來解決單線程中大計算量的問題:child_process。 子進程的出現,意味著Node可以從容地應對單線程在健壯性和無法利用多核CPU方面的問題。通過將計算分發到各個子進程,可以將大量計算分解掉,然後再通過進程之間的事件消息來傳遞結果,這可以很好地保持應用模型的簡單和低依賴。通過Master-Worker的管理方式,也可以很好地管理各個工作進程,以達到更高的健壯性
應用場景
在進行技術選型之前,需要瞭解一項新技術具體適合什麼樣的場景,畢竟合適的技術用在合適的場景可以起到意想不到的效果。關於Node,探討得較多的主要有I/O密集型和CPU密集型
1、I/O密集型
如果將所有的腳本語言拿到一處來評判,那麼從單線程的角度來說,Node處理I/O的能力是值得豎起拇指稱贊的。通常, 說Node擅長I/O密集型的應用場景基本上是沒人反對的。Node面向網路且擅長並行I/O,能夠有效地組織起更多的硬體資源,從而提供更多好的服務
I/O密集的優勢主要在於Node利用事件迴圈的處理能力,而不是啟動每一個線程為每一個請求服務,資源占用極少
2、CPU密集型
換一個角度,在CPU密集的應用場景中,Node是否能勝任呢?實際上,V8的執行效率是十分高的。單以執行效率來做評判,V8的執行效率是毋庸置疑的
CPU密集型應用給Node帶來的挑戰主要是:由於JavaScript單線程的原因,如果有長時間運行的計算(比如大迴圈),將會導致CPU時間片不能釋放,使得後續I/O無法發起。但是適當調整和分解大型運算任務為多個小任務,使得運算能夠適時釋放,不阻塞I/O調用的發起,這樣既可同時享受到並行非同步I/O的 處,又能充分利用CPU