一個打十個,為什麼 NodeJS 比較快?
傳統的服務請求模型
我想先用一個例子來描述傳統 HTTP Service 的 Blocking 工作方式,先讓我說個故事先…
假設有一間快餐店,裡頭有著一個櫃台 (執行程序) 與大廳 (系統資源),這間店雇用了數位服務生 (Thread)。客人進來店裡之後會走向櫃檯和服務生進行點餐 (HTTP Request),點餐完成的同時服務生走向廚房替客人製作餐點 (執行運算或 I/O 處理),這時客人在櫃檯旁邊等待自己的餐點。當餐點製作完成時,服務人員會走到大廳尋找點餐的客人 (Content Switch),並將餐點交給他,最後客人離開大廳完成交易。有個問題,由於在服務人員準備餐點的期間,此時櫃檯並沒有多餘的服務生可以招呼下一位客戶 (Blocking),為了能夠持續服務更多的客人,老闆雇用了更多的服務生 (開了很多 Thread),好讓足夠的服務生可以持續在櫃檯服務新來的客人。
我們想像一下,假設現在碰到旅行團用餐,瞬間湧入大量客人,但一個櫃台只能同時服務一位客人處理點餐手續。這時候老闆為了舒緩客人的等待時間,於是增加了許多櫃台 (Process) 數量,暫時解決了客人等待的問題。
隨著時間的演進,店裡生意越來越好,常常旅行團的遊覽車一次來了好幾台,大廳擠滿了排隊與等待餐點的客人。點餐的服務生除了招呼客戶,還要排除客人插隊所產生的糾紛 (Lock),客人太多太吵,服務生製作完餐點也需要好長一段才可以送到客人手上 (Content Switch)。最後,老闆只好多開幾間店 (Server),投入越來越多的資金,結局你懂 der…….
NodeJS Event-Base 請求模型
那麼 NodeJS 對於大量連線是如何運作的?我們想像一下…
這時對面街上開了一間叫做「諾得接屎」的快餐店,他們的服務流程是這樣的。這間店只有一個櫃檯 (Process),裡頭也只有一位點餐的服務生 (Thread),沒有大廳只有類似「得來速」快速點餐服務。店裡有一個規矩,就是客人要自己填寫菜單與送餐地址 (Callback),點餐時客人會將單子交給櫃檯人員,定餐過程就結束了,客人可以回到自己喜歡的地方等待餐點,快吧?
當點餐服務生拿到點餐單後,會將單子放在一個新的托盤上,接著將托盤放置於等待製作的大桌子上,這個帶著點菜單的托盤我們稱為請求物件 (Request Object)。這時櫃檯服務生可以立馬處理下一位客人,不管來多少人都是要排隊,但是每個客人停留在櫃檯的時間非常短。
那麼廚房如何製作餐點呢?是這樣子的,廚房養了一批廚師 (執行續池),有空的廚師就會到大桌子拿起一個托盤,依照上頭的菜單開始製作餐點 (執行運算或 I/O 處理),並將製作好的餐點 (執行結果) 放置於托盤上,製作完後將托盤放置於另一個等待派送的大桌子上。這時還有另一批負責送餐的服務員,有空就到這個大桌子上拿起一個完成製作的托盤,看看要送到哪裡就將餐點送到客人的手上 (Invoke Callback)。
由於整個過程中工作都是非同步執行,因此過程會充滿很多的變數,不一定先點餐的就會先拿到餐點,但是對於客戶的點餐體驗就快多了。
由了解架構到實務應用
但由於 NodeJS 運作時只能開啟一個程序進行運算 (我猜測這樣的設計是避免過多的 Content Switch),上文描述許多的角色人員只是程序中不同的 Thread,實際上利用 CPU 分時處理的技巧來達成,可別忘了實際上只有一個程序在運作。這個觀念告訴我們,避免程式佔用太長的 CPU 運算時間是很重要的,不然會造成 NodeJS 無法快速接應新的連線請求。
此外,在實務上最大的問題就是 NodeJS 僅能用到 CPU 的一顆核心進行運算 (因為單一程序的緣故),但其實可以透過像是 cluster 或 webworker-threads (實作 HTML5 Worker 機制) 之類的模組來分散工作到其他的 CPU 上。了解 NodeJS 運作的原理與特性相當重要,錯誤的設計 NodeJS 程式碼將可能帶來反效果,無法發揮 NodeJS 的最大功效。
上述描述的故事對於 NodeJS 的實際運作機制其實有些出入,我想僅能讓各位了解 NodeJS 大致的運作概念,不好意思獻醜了…….哈
「原文轉載:http://blog.toright.com」