Node.js異步機制和基本原理

一、Node.js優缺點

node.js是單線程。

好處就是

1)簡單

2)高性能,避免了頻繁的線程切換開銷

3)佔用資源小,因爲是單線程,在大負荷情況下,對內存佔用仍然很低

3)線程安全,沒有加鎖、解鎖、死鎖這些問題

php


node.js


壞處就是

如何解決高併發?

node使用異步IO和事件驅動(回調函數)來解決這個問題。

一般來說,高併發解決方案會提供多線程模型,爲每個業務邏輯提供一個線程,通過系統線程切換來來彌補同步I/O調用的時間開銷。像apache,是一個請求一個線程。

而node.js使用的是單線程模型,對所有I/O都採用異步的請求方式,避免頻繁的上下文切換,在node.js執行的時候維護着一個事件隊列;程序在執行時進入事件循環等待下一個事件到來,每個異步I/O請求完成後都會被推送到事件隊列中的等待執行。

比如說:

對於一個簡單的數據庫訪問操作,傳統方式是這樣實現的

[javascript] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. res = db.query('SELECT * from some_table');  
  2. res.output();  
代碼執行到第一行的時候線程會阻塞,等待query返回結果,然後繼續處理。由於數據庫查詢、磁盤讀寫、網絡通信等原因(所謂的I/O)阻塞時間會非常大(相對於CPU始終頻率)。對於高併發的訪問,一方面線程長期阻塞等待,另一方面爲了應付新情求而不斷添加新線程,會浪費大量系統資源,同時線程的增加也會也會佔用大量的CPU時間來處理內存上下文切換。看看node.js怎麼處理

[javascript] view plaincopy
  1. db.query('SELECT * from some_table'function(res) {   
  2.    res.output();  
  3. });  

query的第二個參數是一個回調函數,進程執行到db.query的時候不會等待結果返回,而是直接繼續執行下面的語句,直到進入事件循環。當數據庫執行結果返回的時候會將事件發送到事件隊列,等到線程進入事件循環後纔會調用之前的回調函數。

node.js的異步機制是基於事件的,所有的I/O、網絡通信、數據庫查詢都以非阻塞的方式執行,返回結果由事件循環來處理。node.js在同一時刻只會處理一個事件,完成後立即進入事件循環檢查後面事件。這樣CPU和內存在同一時間集中處理一件事,同時儘量讓耗時的I/O等操作並行執行。


事件循環機制

所謂事件循環是指node.js會把所有的異步操作使用事件機制解決,有個線程在不斷地循環檢測事件隊列。

node.js中所有的邏輯都是事件的回調函數,所以node.js始終在事件循環中,程序入口就是事件循環第一個事件的回調函數。事件的回調函數中可能會發出I/O請求或直接發射( emit)事件,執行完畢後返回事件循環。事件循環會檢查事件隊列中有沒有未處理的事件,直到程序結束。node.js的事件循環對開發者不可見,由libev庫實現,libev不斷檢查是否有活動的、可供檢測的事件監聽器,直到檢查不到時才退出事件循環,程序結束。

如圖所示


 libuv 是一個高性能事件驅動的程序庫,封裝了 Windows 和 Unix 平臺一些底層特性,爲開發者提供了統一的 API.
因此,node.js 是單線程,異步非阻塞。

但畢竟,如何彌補單線程缺陷?是不是有異步非阻塞,就可以高枕無憂了?

不是的。

1)CPU密集型任務存在短板

如上所述,nodejs的機制是單線程,這個線程裏面,有一個事件循環機制,處理所有的請求。如圖所示。在事件處理過程中,它會智能地將一些涉及到IO、網絡通信等耗時比較長的操作,交由worker threads去執行,執行完了再回調,這就是所謂的異步IO非阻塞吧。但是,那些非IO操作,只用CPU計算的操作,它就自己扛了,比如算什麼斐波那契數列之類。它是單線程,這些自己扛的任務要一個接着一個地完成,前面那個沒完成,後面的只能乾等。因此,對CPU要求比較高的CPU密集型任務多的話,就有可能會造成號稱高性能,適合高併發的node.js服務器反應緩慢。



2)無法利用CPU的多核

最開始,線程只是用於分配單個處理器處理時間的一種機制。但假如操作系統本身支持多個CPU/內核,那麼每個線程都可以得到一個不同自己的CPU/內核,實現真正的“並行運算”。在這種情況下,多線程程序可以提高資源使用效率。Node.js是單線程程序,它只有一個event loop,也只佔用一個CPU/內核。現在大部分服務器都是多CPU或多核的,當Node.js程序的event loop被CPU密集型的任務佔用,導致有其它任務被阻塞時,卻還有CPU/內核處於閒置的狀態,造成資源的浪費。


解決方案

利用原生模塊或第三方模塊,開闢進程或子進程,用於處理這些特殊的任務。

3)如果有異常拋出,因爲是單線程,整個項目將不可用。但這歸根到底是代碼的問題,糟糕的代碼,不管什麼體系,都會有問題,即使不崩潰。解決辦法是用pm2等工具來運行?

二、nodejs與JavaScript的關係

nodejs本身不是開發語言,它是一個工具或者平臺,在服務器端解釋、運行javascript;coffeescript屬於nodejs體系,算是一種新的開發語言,但它的目的在於最後編譯成javascript。

nodejs利用Google V8來解釋運行javascript,但是系統真正執行的代碼是用C++寫的。javascript做的只是調用這些API而已。因此,並無執行效率的問題。



三、nodejs適用場景


1、RESTful API

這是適合 Node 的理想情況,因爲您可以構建它來處理數萬條連接。它仍然不需要大量邏輯;它本質上只是從某個數據庫中查找一些值並將它們組成一個響應。由於響應是少量文本,入站請求也是少量的文本,因此流量不高,一臺機器甚至也可以處理最繁忙的公司的 API 需求。

2、實時程序

比如聊天服務

聊天應用程序是最能體現 Node.js 優點的例子:輕量級、高流量並且能良好的應對跨平臺設備上運行密集型數據(雖然計算能力低)。同時,聊天也是一個非常值得學習的用例,因爲它很簡單,並且涵蓋了目前爲止一個典型的 Node.js 會用到的大部分解決方案。

3、單頁APP

ajax很多。現在單頁的機制似乎很流行,比如phonegap做出來的APP,一個頁面包打天下的例子比比皆是。


。。。


總而言之,NodeJS適合運用在高併發、I/O密集、少量業務邏輯的場景



參考文章

關於node.js的誤會

Node.js的線程和進程

http://www.slideshare.net/mysqlops/nodejs-9313477

Node.js軟肋之CPU密集型任務

http://www.ruanyifeng.com/blog/2014/10/event-loop.html

http://segmentfault.com/a/1190000000375619

http://www.cnblogs.com/sysuys/p/3460614.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章