nodejs第一天

小烜同學發表於2019-02-27

Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient.

  我們來到Node的官方網站的時候就能看到這麼一句話,這句話體現了node的幾大特點,我們來一一細看。

一、 Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine.

  我們學習一個東西,首先我們要知道這東西是用來幹什麼的。以前我聽說node的時候以為nodejs是一個框架,當了解到node之後就認為這只是在服務端的javascript,但是事實的真相是這樣的麼?我們來看一看上面這段話。Node.js是一個構建在Chrome的V8引擎上的javascript的執行時,可能你不太能理解執行時是什麼,其實在我的理解中就和Java的JRE(Java Runtime Environment)是差不多的,他提供了API環境、執行環境,讓我們的js在服務端可以跑起來,同時提供了可以對檔案系統、網路等等進行操作的api,這是讓前端工程師無比興奮的突破。

二、 Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient.

  接下來的這句話體現了Node的幾大特性:

  • 事件驅動
  • 非阻塞I/O模型
  • 單執行緒(裡面沒提,但是我覺得這也是一大特性)

  再說上面的幾個特性之前,我們先來看一看,在我學習的途中經常會看到有人說Node在處理高併發、I/O密集型服務是具有優勢的,那麼我的疑問是I/O密集型是啥,搜尋資料後發現,跟I/O密集相對應的是CPU密集,我們可以看一看這兩者之間的區別。

  • I/O密集:如果一個程式大部分消耗的時間都是用來對檔案的I/O操作,資料庫的操作,或者是網路操作,存取這一類的操作,那麼我們就可以說這是I/O密集型的服務。
  • CPU密集: 如果一個程式的時間都消耗在計算,解壓,壓縮等等需要CPU來進行操作的話,那麼我們就可以說這是一個CPU密集型的服務。

  好了我們看完這兩者之間的區別,我們也就能瞭解,為啥Node適合web開發,你想一想我們在進行web開發的時候,我們進行的都是些什麼操作,靜態資源讀取、網路操作、資料庫的增刪改查,我們可以發現web場景中I/O操作是最耗時的操作。現在CPU的提速我們執行指令的速度是很快的,但是我們進行I/O操作還是非常耗時的。我們可以看到web是一個很典型的I/O密集型服務,那麼我們為什麼說Node就非常適合web場景呢?我們等會兒再談。

  下面我們來看一看高併發的情況,在《深入淺出NodeJS》這本書中我們能看到樸靈老師對服務模型的見解,我覺得很能說明問題,我們一起來看一看:

  1. 石器時代:同步
      在很久很久以前,我們伺服器是同步的,一次只能服務一個請求,就比如說我們一個飯店,請了一個廚子一個服務員,我們現在只有一個廚子,所以我們現在只能接收一個客人的點餐,等上個客人的做好了我們才能繼續進行下面一位客人的點餐,我們都知道這樣的效率肯定是不行的,所以又有了下面的演進。

  2. 青銅時代:複製程式
      我們可以通過程式的複製,一個程式服務一個請求,這樣我們就能夠服務多個請求了,但是夢想是很美好的,理想很豐滿。繼續用上面的例子,我們飯店大起來了,一個廚子這樣已經忙不過來了,這時候,我們能想到的是什麼,再多請幾個廚子,這樣也能夠解決多個客人的問題,但是我們想了一下,請這麼多廚子,工資誰發啊,太貴了。這也是複製程式的一個缺點,程式的複製是非常昂貴的,我們要複製程式內的狀態,而且程式內還會有很多相同的狀態會造成浪費,那麼又該怎麼辦呢?

  3. 白銀時代:多執行緒
      我們從上面的角度上來想,我們速度慢的操作也就是讀取的操作,不需要很厲害的廚子,小廚子就行了,於是乎我們就炒掉了大廚,用大廚的錢請了幾個小廚子,這樣我們的小廚子也能夠進行炒菜,所以我們接到點餐之後,就給這幾個小廚子做。但是呢我們一個服務員對應的廚子是有限的,我們可以想象,點菜這個操作是非常快的,可能服務員點完菜之後就開始玩了,等廚子做好了才會端上來,這就造成了CPU的浪費,同時呢,對於多執行緒來說,佔用記憶體空間,執行緒的上下文切換等等問題也是無法做到很好的滿足需求。

  4. 黃金時代:事件驅動
      為了解決高併發問題,基於事件驅動的服務模型就出現了,就像Node和Nginx都是基於事件驅動的,採用單執行緒就避免了不必要的記憶體開銷和上下文切換。看上面的例子,其實我們僱一個服務員,排隊叫號,然後就接待下一個客人,不用等這個客人的菜好了才服務下一個,等廚子做好了菜就直接給服務員說菜做好了,然後叫號,客人來取就OK了。

單執行緒

  借用一張老師的圖,其實這張圖其實我們能看得很清楚,我們JS是單執行緒的,node中也是這樣的,客戶端的N個請求,我們接收到之後,是直接將耗時得操作放到了後面的執行緒池裡去操作,使用單執行緒可以避免記憶體的消耗,以及上下文的切換。同時,我們利用事件驅動,可以在後面的執行緒I/O操作完成後以事件的方式通知主執行緒,然後返回結果。
  其實說到這裡,有些人就和我一樣困惑了,不是說nodejs是單執行緒麼,怎麼又冒出了多執行緒。其實我們想一想也能夠明白,如果nodejs只是靠單執行緒工作,那麼他怎麼完成高併發的web服務呢?我們可以這麼說,nodejs的單執行緒是針對主程式的,而非同步操作、I/O操作等等都是靠作業系統底層的多執行緒排程了,nodejs的單執行緒只是負責排程,就像飯店的服務員一樣,只負責接單,做菜的都是後臺的廚師。還有就是nodejs是單執行緒,那麼現在的多核CPU該怎麼去使用呢?其實不用我們擔心,node是考慮了這樣的情況的,nodejs是有專門的模組來進行操作的。

事件驅動

  那麼事件驅動又是個什麼東西呢?其實,我們在做前端開發的時候是經常和事件打交道的,比如ajax,我們發起一個請求,然後監聽事件,成功之後就執行回撥。其實這也是node的一大特點,就像node在接收請求一樣的,node接收到請求之後,將對應的I/O操作交給作業系統,然後監聽事件,當I/O操作完成之後,觸發事件執行回撥拿到資料。

非阻塞I/O

  阻塞I/O就是當使用者發一個讀取檔案的操作的時候,程式就會被阻塞,直到要讀取的資料全部準備好返回給使用者。那非阻塞I/O呢,就是使用者發起一個讀取檔案操作的時,函式立即返回,不作任何等待,程式繼續執行,當讀取操作完成之後,主程式再去拿資料。那程式如何知道要讀取的資料已經準備好了呢?最簡單的方法就是輪詢,這個在樸靈老師的書裡面也提到了幾種方式。


  今天就簡單的學習了一下node,如果有什麼地方不對的請多指教,也是才學習nodejs。