1. 程式人生 > >Node.js應用實戰和工作原理解析

Node.js應用實戰和工作原理解析

Node.js是一個基於Chrome JavaScript執行時建立的開發平臺, 用於方便地搭建響應速度快、易於擴充套件的網路應用。Node.js 使用事件驅動,非阻塞I/O模型而得以輕量和高效,非常適合在分散式裝置上執行資料密集型的實時應用,例如移動應用裡的訊息模組。

1.jpg

    為滿足雲智慧透視寶使用者對Node.js的程式碼級效能監控需求,我們的程式猿Else對Node.js的工作原理和執行機制進行了大量的深入研究,而本次分享正是來自Else的心得體會。

    本文分為倆個部分:

    第一部分:通過應用層面,給大家演示什麼Node.js,它能做什麼,怎麼去做。

    第二部分:在大家對Node.js有個基本認識之後,從理論層面談談Node.js的執行機制。

    Node.js應用解析

    Node.js是能夠跨平臺的,今晚我們分享將基於windows,但linux或者macro大致相同。

    nodejs環境搭建

    首先登陸node官網下載安裝包,地址是:nodejs.org/en/

2.jpg

    下載完成之後,安裝步驟就是傻瓜式的下一步下一步:

3.jpg

    安裝完成之後在windows下開啟powershell,然後執行 " node -v "就能檢視到我們node的版本資訊,這也就說明我們的node已經安裝成功。

4.jpg

    第一個demo

    之後我們找個目錄新建一個專案資料夾,然後在專案資料夾下面新建一個hello__.js的檔案,將如下程式碼寫入hello__.js的檔案:

5.jpg

    而後我們cd到專案目錄下,執行node hello__.js

6.jpg

    然後在瀏覽器裡輸入127.0.0.1:8899就能直接訪問我們的服務了

7.jpg

    我們在程式碼中用了 var http = require("http"); 這種方式引用了node的一個自身模組http,模組可以理解成DotNet的類庫、Java的包,然後應用這個http模組快速建了一個服務,這也是node給開發人員帶來的便利。

    NPM

    說到便利,對於node.js還必須要提到NPM,在上個demo中我們用到var http = require("http"); 來引用模組,我們也說到了http是node自身的模組,那麼如果需要引用一些node的非自身模組,應該如何做呢?

    這個任務的第一步就交給NPM,在安裝node的同時已經將NPM工具安裝完成,我們只需要執行NPM -v就能檢視NPM的版本,直接輸入NPM可以檢視相關幫助。

    NPM的最常見用法就是安裝依賴模組,當安裝完成之後我們的專案就能對安裝的依賴模組進行引用。現在我們拿 express模組來舉個栗子,首先需要cd 到專案的node_modules目錄下,然後執行npm install express:

8.jpg

    當你看到這樣的介面的時候就說明模組已經安裝成功,回到專案目錄下就能看見模組目錄下新增了一個express目錄,然後在專案目錄下建立一個hello.js的指令碼檔案,檔案內容如下:

9.jpg

    下一步就是啟動這個專案,直接執行 node .\hello.js

10.jpg

    然後在瀏覽器裡輸入地址127.0.0.1:8081

11.jpg

    這種方式引用模組非常方便。那麼我們是不是也可以編寫自己的第三方模組上傳到npm庫給他人使用呢?答案是肯定的!接下再舉個栗子給大家說明下,首先在專案根目錄下建立一個npmtest資料夾:

12.jpg

    在當前目錄下建立一個hello.js檔案,內容如下:

13.jpg

    然後執行npm init 命令,在執行命令之後會要求你輸入一些模組打包的描述資訊,這些資訊最終會生成一個包的描述檔案叫package.json

14.jpg

    輸入所有資訊之後會問你"Is this ok?",輸入yes就完成了打包過程。檢視資料夾下面會增加一個描述包檔案:

15.jpg

    然後大家再look下我們生成描述檔案的內容:

16.jpg

    可以看到這些資訊都是剛才輸入的內容。包打好了,接下來就是上傳到倉庫,上傳倉庫前我們先註冊一個賬號npm adduser,然後輸入使用者名稱、密碼還有郵箱地址就可以了。

    現在萬事具備了,東風來吧,執行npm publish ,就可以把我們包上傳了:

17.jpg

    注意publish後面有一個點 ”.“。當我們的包上傳完成之後,通過呼叫來做個驗證,換個目錄下來執行 npm install cloudwise_else_npmtest:

18.jpg

    在我們的目錄裡檢視,就知道建立的模組已經引用過來了:

19.jpg

    上面我們介紹Node.js的環境搭建,模組安裝、引用,以及如何建立和引用自己的模組,並對每一個點都做了相應的示例。

    除錯工具

    現在我們已經能夠讓專案run起來了,專案執行過程中如果出現異常怎麼辦,這是程式猿尤為關心的。在我接觸的PHP開發者中,他們比較習慣用日誌去跟蹤,本人是DotNet的忠實粉絲,江湖流傳宇宙最強IDE的Visual Studio給DotNet開發者提供了非常強大的除錯功能,可以讓我們隨心所欲的除錯,快速準確的定位到Bug。所以日誌除錯讓我很不能接受,如此火熱的Node.js肯定也給開發者提供了不錯的debug工具。

    首先還是用npm來安裝,命令如下:

    npm install -g node-inspector

    啟動的時候我們會在中間加一個debug,命令如下:

    node debug .\hello.js

    接下來我們啟動除錯外掛 node-inspector.cmd,此處linux跟windows有點差別,linux下沒有後面cmd,然後開啟瀏覽器(當前此外掛只支援chrome跟Opera),地址如下:http://127.0.0.1:8080/?port=5858

20.jpg

    當我們在程式碼中打斷點的時候,程式就會捕獲到斷點,然後在這裡新增監視,也可以單步執行或者逐過程逐語句,愛怎麼玩就怎麼玩。說到這裡我們對Node.js是什麼、能做什麼、怎麼做有了基本的認識,那麼是不是就可以開始coding工作了呢?

    完全可以的!有人或許會問,Node.js能做的事情php、java、.net也都能做,為什麼要選擇Node.js呢,難道單單是因為他不用去搭建Apache、IIS、Tomcat嗎?接下來我就根據自己的理解和大家探討下Node.js能夠在最近幾年被聚光的緣由。

    Node.js執行機制解析

    當我們搜尋Node.js時,奪眶而出的關鍵字就是 "單執行緒,非同步I/O,事件驅動",應用程式的請求過程可以分為倆個部分:CPU運算和I/O讀寫,CPU計算速度通常遠高於磁碟讀寫速度,這就導致CPU運算已經完成,但是不得不等待磁碟I/O任務完成之後再繼續接下來的業務。

    所以I/O才是應用程式的瓶頸所在,在I/O密集型業務中,假設請求需要100ms來完成,其中99ms化在I/O上。如果需要優化應用程式,讓他能同時處理更多的請求,我們會採用多執行緒,同時開啟100個、1000個執行緒來提高我們請求處理,當然這也是一種可觀的方案。

    但是由於一個CPU核心在一個時刻只能做一件事情,作業系統只能通過將CPU切分為時間片的方法,讓執行緒可以較為均勻的使用CPU資源。但作業系統在核心切換執行緒的同時也要切換執行緒的上線文,當執行緒數量過多時,時間將會被消耗在上下文切換中。所以在大併發時,多執行緒結構還是無法做到強大的伸縮性。

    那麼是否可以另闢蹊徑呢?!我們先來看看單執行緒,《深入淺出Node》一書提到 "單執行緒的最大好處,是不用像多執行緒程式設計那樣處處在意狀態的同步問題,這裡沒有死鎖的存在,也沒有執行緒上下文切換所帶來的效能上的開銷",那麼一個執行緒一次只能處理一個請求豈不是無稽之談,先讓我們看張圖:

21.jpg

    Node.js的單執行緒並不是真正的單執行緒,只是開啟了單個執行緒進行業務處理(cpu的運算),同時開啟了其他執行緒專門處理I/O。當一個指令到達主執行緒,主執行緒發現有I/O之後,直接把這個事件傳給I/O執行緒,不會等待I/O結束後,再去處理下面的業務,而是拿到一個狀態後立即往下走,這就是“單執行緒”、“非同步I/O”。

22.jpg

    I/O操作完之後呢?Node.js的I/O 處理完之後會有一個回撥事件,這個事件會放在一個事件處理佇列裡頭,在程序啟動時node會建立一個類似於While(true)的迴圈,它的每一次輪詢都會去檢視是否有事件需要處理,是否有事件關聯的回撥函式需要處理,如果有就處理,然後加入下一個輪詢,如果沒有就退出程序,這就是所謂的“事件驅動”。

    本瞭解了非同步I/O、單執行緒、事件驅動這幾個Node的標籤,這裡再引入一個觀察者的概念,每次輪詢都會去向觀察者詢問是否有事件需要處理,這個過程就如同飯館的後廚,廚房一輪一輪的製作菜餚,具體做什麼菜取決於餐廳裡客人的下單,廚房做完成就詢問收銀小妹接下來做什麼菜,而收銀小妹就是觀察者,他收到的客人點單就是關聯的回撥函式,如果生意好的飯館會有多個收銀小妹,就如同事件迴圈中有多個觀察者,收到下單就是一個事件,一個觀察者裡頭可能有多個事件。

    在node.js中,事件主要來源於網路請求,檔案I/O等,根據事件的不同對觀察者進行了分類,有檔案I/O觀察者,網路I/O觀察者。事件驅動是一個典型的生產者/消費者模型,請求到達觀察者那裡,事件迴圈從觀察者進行消費,主執行緒就可以馬不停蹄的只關注業務不用再去進行I/O等待。

    那麼您可能會問,這種單個執行緒進行運算,對於多核CPU的伺服器豈不是英雄無用武之地,還有就是當主執行緒業務運算超時,豈不是來不及處理事件佇列裡(觀察者裡頭)的事件?

    對於這倆個問題,首先要做的一點就是在程式碼編寫的時候儘量避免耗時的計算,將大計算進行拆分,這樣能夠讓主執行緒及時得到釋放,處理消費事件佇列裡頭的事件。其次,node.js提供了child_process模組開啟子程序,理想狀態下每個程序各自利用一個CPU,以此實現多核的利用,child_precess提供建立子程序,以及程序狀態監控,程序之間通訊的API,感興趣的小夥伴可以問問度娘,或者歡迎私聊。

    聊到這裡,我們就可以回顧前面的問題,為什麼在有PHP、Java、DotNet的今天我們還會去選擇Node.js,因為它的單執行緒、非同步I/O、事件驅動特點能夠更好的處理I/O密集型的業務場景,同時它在多核CPU利用上面也做的非常優秀,這就是他存在的理由!

    當然,如果你的業務場景幾乎沒有任何I/O操作,屬於純CPU密集型的業務,那最好還是選擇一種多執行緒語言。

    一個Node.js飯店的發展歷程

    前面的一堆理論似乎不太好明白,最後講一個關於飯店發展歷程的故事作為結尾吧。

    第一年

    飯店開張,只有一個廚師(同時還兼任老闆、服務員、打荷、收銀員),當一個客人點餐之後,這個廚師就開始記錄(服務員),然後他就開始備菜(打荷)、炒菜(廚師)、然後上菜(服務員)、收錢(收銀員),這個時候即使有其他客人來了,等著吧還沒忙完呢。這個廚師就這樣兢兢業業,有條不紊的幹著每一件事,因為每件事都是親力親為,都不能出錯,雖然所有的事情都瞭然於心,但效率很低,一天只能賣出十多份飯菜。

這是飯店單執行緒的第一年:

    利:它沒有執行緒上下文交換所帶來效能上的開銷(因為每件事都是親力親為);

    弊:無法利用多核CPU(廚房空間那麼大,完全可以很多人一起幹活),同時錯誤會引起整個應用退出,應用的健壯性值得考驗(當這個廚師生病,或者有事了飯店就得停業)

    第二年

    這個廚師第一年賺了點錢,回到老家把表哥、表弟全拉過來,現在他們有5個人,可工作方式還是跟廚師第一年的時候一樣。當客人來了,就會有一個人去記錄,然後自己去廚房洗菜、切菜、炒菜、洗碗、上菜、收錢。當來了第六個客人的時候,就要等待前面的人做完所有事情才能空出一個人來接待。後來他們就想既然客戶多了,廚師就得多,再回老家多叫幾個兄弟吧。這時新的問題發生了,當每個廚師做完飯後就出去找客人,客人說我剛剛點餐了,然後廚師就去廚房問,剛剛是哪個表兄接待的那個客戶,要是沒有人接待的話,我來處理。就這樣忙忙碌碌一年,他們比去年多做了好多生意,但是感覺每天客戶多的時候,廚房裡頭亂糟糟的,總要詢問這個詢問那個。

    這就是飯店多執行緒的一年:

    利:一個執行緒服務一個請求,執行緒之間可以共享資料,這樣可以避免記憶體的浪費,可以同時處理多個請求;

    弊:作業系統核心在切換執行緒的同時也要切換執行緒的上下文,當執行緒數量過多時,時間將會被消耗在上下文切換上(廚房裡頭亂糟糟的,總要詢問這個詢問那個)。

    第三年

    老闆娘過來了(Node.Js閃亮登場),她發現這幫廚師都在各自為戰,自己拿到客戶的點餐後去洗菜、切菜、洗碗、炒菜、上菜、收錢,一個人只能同時處理一個任務,而且作為廚師沒必要去做洗菜、切菜、洗碗、收銀之類費時的工作。

    所以老闆娘把所有人進行了分工:

    老闆作為廚師長(Node裡頭的主執行緒),他不再去洗碗、洗菜、切菜、炒菜、收銀(我們可以把洗碗、洗菜、炒菜、切菜認為是比較耗時的I/O),他只負責將收銀臺小妹(觀察者)拿過來的選單分配給不同的廚師,打荷(這些人就是不同的I/O執行緒),吩咐下去之後他不會等菜出來再走(進入下一個輪詢),又問收銀臺小妹還有沒有選單要做,如果有繼續輪詢,如果沒有了休息(退出程序)。當菜做出來之後放在上菜區(回撥),收銀臺就顯示菜出來了(將回調放入事件佇列),當老闆查詢收銀員(觀察者)的時候,收銀員就告訴廚師長,廚師長就通知服務員(處理不同I/O的執行緒)上菜(完成回撥),這樣飯店有條不紊的執行下去,客人也越來越多了。

    有些高階客戶不想在這擠,所以老闆娘就想,飯店房子那麼大(多核CPU),可以叫她弟弟(子程序的主執行緒)在這開個子店(child_process),然後發現一個收銀員不夠用,那就多招幾個收銀員(多個觀察者),就這樣每個分店(程序)只有一個主管(主執行緒),主管的弱點就是無暇顧及洗碗等雜活(I/O),只能關注業務,至於飯店能開多大(應用程式能處理多大請求),要看主管的處理能力(主執行緒的程式設計強壯度)。最後,大飯店起了個時髦的名字叫Hotel NodeJs!

    希望這個小故事能夠給大家對Node.Js執行的理解帶來一點幫助,謝謝。