Node.js非同步I/O學習筆記,node.js學習筆記
“非同步”這個名詞的大規模流行是在Web 2.0浪潮中,它伴隨著Javascript和AJAX席捲了Web。但在絕大多數進階程式設計語言中,非同步並不多見。PHP最能體現這個特點:它不僅屏蔽了非同步,甚至連多線程也不提供,PHP都是以同步阻塞的方式來執行。這樣的優點利於程式猿順序編寫商務邏輯,但在複雜的網路應用中,阻塞導致它無法更好地並發。
在伺服器端,I/O非常昂貴,分布式I/O更加昂貴,只有後端能快速響應資源,前端的體驗才能變得更好。Node.js是首個將非同步作為主要編程方式和設計理念的平台,伴隨著非同步I/O的還有事件驅動和單線程,它們構成Node的基調。本文將介紹Node是如何?非同步I/O的。
1. 基本概念
“非同步”與“非阻塞”聽起來似乎是一回事,從實際效果而言,這兩者都達到了並行的目的。但是從電腦核心I/O而言,只有兩種方式:阻塞與非阻塞。因此非同步/同步和阻塞/非阻塞實際上是兩回事。
1.1 阻塞I/O與非阻塞I/O
阻塞I/O的一個特點是調用之後一定要等到系統核心層面完成所有操作後,調用才結束。以讀取磁碟上的一個檔案為例,系統核心在完成磁碟尋道、讀取資料、複製資料到記憶體中後,這個調用才結束。
阻塞I/O造成CPU等待I/O,浪費等待時間,CPU的處理能力不能得到充分利用。非阻塞I/O的特點就是調用之後會立即返回,返回後CPU的時間片可以用來處理其他事務。由於完整的I/O並沒有完成,立即返回的並不是業務層期待的資料,而僅僅是當前調用的狀態。為了擷取完整的資料,應用程式需要重複調用I/O操作來確認是否完成(即輪詢)。輪詢技術要以下幾種:
1.read:通過重複調用來檢查I/O狀態,是最原始效能最低的一種方式
2.select:對read的改進,通過對檔案描述符上的事件狀態來進行判斷。缺點是檔案描述符最大的數量有限制
3.poll:對select的改進,採用鏈表的方式避免最大數量限制,但描述符較多時,效能還是十分低下
4.epoll:進入輪詢時若沒有檢查到I/O事件,將會進行休眠,直到事件發生將其喚醒。這是當前Linux下效率最高的I/O事件通知機制
輪詢滿足了非阻塞I/O確保擷取完整資料的需求,但對於應用程式而言,它仍然只能算作一種同步,因為依然需要等待I/O完全返回。等待期間,CPU要麼用於遍曆檔案描述符的狀態,要麼用於休眠等待事件發生。
1.2 理想與現實中的非同步I/O
完美的非同步I/O應該是應用程式發起非阻塞調用,無需通過輪詢就可以直接處理下一個任務,只需在I/O完成後通過訊號或回調將資料傳遞給應用程式即可。
現實中的非同步I/O在不同作業系統下有不同的實現,如*nix平台採用自訂的線程池,Windows平台採用IOCP模型。Node提供了libuv作為抽象封裝層來封裝平台相容性判斷,並保證上層Node與下層各平台非同步I/O的實現各自獨立。另外需要強調的是我們經常提到Node是單線程的,這僅僅是指Javascript的執行在單線程中,實際在Node內部完成I/O任務的都另有線程池。
2. Node的非同步I/O
2.1 事件迴圈
Node的執行模型實際上是事件迴圈。在進程啟動時,Node會建立一個無限迴圈,每一次執行迴圈體的過程成為一次Tick。每個Tick過程就是查看是否有事件等待處理,如果有則取出事件及其相關的回呼函數,若存在關聯的回呼函數則執行它們,然後進入下一個迴圈。如果不再有事件處理,就退出進程。
2.2 觀察者
每個事件迴圈中有若干個觀察者,通過向這些觀察者詢問來判斷是否有事件要處理。事件迴圈是一個典型的生產者/消費者模型。在Node中,事件主要來源於網路請求、檔案I/O等,這些事件都有對應的網路I/O觀察者、檔案I/O觀察者等,事件迴圈則從觀察者那裡取出事件並處理。
2.3 請求對象
從Javascript發起調用到核心執行完I/O操作的過渡過程中,存在一種中間產物,叫做請求對象。以最簡單的Windows下fs.open()方法(根據指定路徑和參數去開啟一個檔案並得到一個檔案描述符)為例,從JS調用到內建模組通過libuv進行系統調用,實際上是調用了uv_fs_open()方法。在調用過程中,建立了一個FSReqWrap請求對象,從JS層傳入的參數和方法都封裝在這個請求對象中,其中我們最為關注的回呼函數被設定在這個對象的oncompete_sym屬性上。對象封裝完畢後,將FSReqWrap對象推入線程池中等待執行。
至此,JS調用立即返回,JS線程可以繼續執行後續操作。當前的I/O操作線上程池中等待執行,這就完成了非同步呼叫的第一階段。
2.4 執行回調
回調通知是非同步I/O的第二階段。線程池中的I/O操作調用完畢後,會將擷取的結果儲存起來,然後通知IOCP當前對象操作已完成,並將線程歸還線程池。在每次Tick的執行中,事件迴圈的I/O觀察者會調用相關的方法檢查線程池中是否有執行完的請求,如果存在,會將請求對象加入到I/O觀察者的隊列中,然後將其當做事件處理。
3. 非I/O的非同步API
Node中還存在一些與I/O無關的非同步API,例如定時器setTimeout()、setInterval(),立即非同步執行任務的process.nextTick()和setImmdiate()等,這裡略微介紹一下。
3.1 定時器API
setTimeout()和setInterval()瀏覽器端的API是一致的,它們的實現原理與非同步I/O類似,只是不需要I/O線程池的參與。調用定時器API建立的定時器會被插入到定時器觀察者內部的一棵紅/黑樹狀結構中,每次事件迴圈的Tick都會從紅/黑樹狀結構中迭代取出定時器對象,檢查是否超過定時時間,若超過就形成一個事件,回呼函數立即被執行。定時器的主要問題在於它的定時時間並非特別精確(毫秒級,在容忍範圍內)。
3.2 立即非同步執行任務API
在Node出現之前,很多人也許為了立即非同步執行一個任務,會這樣調用:
複製代碼 代碼如下:
setTimeout(function() {
// TODO
}, 0);
由於事件迴圈的特點,定時器的精確度不夠,而且採用定時器需要使用紅/黑樹狀結構,各種操作時間複雜度為O(log(n))。而process.nextTick()方法只會將回呼函數放入隊列中,在下一輪Tick時取出執行,複雜度為O(1)更為高效。
此外還有一個setImmediate()方法和上述方法類似,都是將回呼函數順延強制。不過前者的優先順序要比後者高,這是因為事件迴圈對觀察者的檢查是有先後順序的。另外,前者的回呼函數儲存在一個數組中,每輪Tick會將數組中的所有回呼函數全部執行完;後者結果儲存在鏈表中,每輪Tick只會執行一個回呼函數。
4. 事件驅動與高效能伺服器
前面以fs.open()為例闡述了Node如何?非同步I/O。事實上對網路通訊端的處理,Node也應用了非同步I/O,這也是Node構建Web伺服器的基礎。經典的伺服器模型有:
1.同步式:一次只能處理一個請求,其餘請求都處於等待狀態
2.每進程/每請求:為每個請求啟動一個進程,但系統資源有限,不具備擴充性
3.每線程/每請求:為每個請求啟動一個線程。線程比進程要輕量,但每個線程都佔用一定記憶體,當大並發請求到來時,記憶體很快就會用光
著名的Apache採用的就是每線程/每請求的形式,這也是它難以應對高並發的原因。Node通過事件驅動方式處理請求,可以省掉建立和銷毀線程的開銷,同時作業系統在調度任務時因為線程較少,環境切換的代價也很低。即使在大量串連的情況下,Node也能有條不紊地處理請求。
知名伺服器Nginx也摒棄了多線程的方式,採用和Node一樣的事件驅動方式。如今Nginx大有取代Apache之勢。Nginx採用純C編寫,效能較高,但是它僅適合做Web伺服器,用於反向 Proxy或負載平衡等。Node可以構建與Nginx相同的功能,也可以處理各種具體業務,自身效能也不錯。在實際項目中,我們可以結合它們各自有點,以達到應用的最佳效能。
Nodejs中有個Fibonacci的非同步例子,疑問processnextTick()作用
你運行兩個fibonacciAsync()就能看出是非同步了。
程式員怎說服老闆採用Nodejs?
導讀:近期以來Node.js在業界很火,有關它的的新聞不勝枚舉,種種跡象表明業界更多的公司在關注和考慮採用Node.js。俗話說“巧婦難為無米之炊”,程式員該如何成功說服老闆聽取您的建議?針對這一話題,作者Felix發表了一篇博文,文中分享了一些建設性指南,CSDN研發頻道現將此文進行編譯,分享給開發人員,也歡迎大家發表自己Node.js實戰心得。糟糕的使用案例Apps在CPU效能上的高使用率 儘管一直鐘情於Node.js,但這裡有幾個使用案例,結果卻並不令人如意。最明顯的是Apps在CPU上的使用率以及I/O操作是極其高負荷的。因此,如果你打算寫一個視頻編碼軟體,人工智慧或者類似CPU使用率比較高的軟體,那麼請不要使用Node.js,使用C或者C++效果會更好一些。話雖如此,但Node.js允許你輕鬆的編寫C++外掛程式,因此,你可以將它作為一個超級演算法的指令碼引擎。簡單的CRUD/HTML AppsNode.js最終會成為一款不錯的編寫Web應用的工具。但是,你不能指望它能像PHP,Ruby,Python那樣為你提供更多的好處。也許你的應用程式會因此而獲得更多的可擴充性,但並不會因為用Node.js編寫的而為你帶來更多的訪問量。當我們看到Node.js一些不錯的架構時,或許你會因此而欣喜不已。事實上,至今還沒有比Rails,CakePHP或者Django這些架構更具強大的應用功能。如果你的應用程式只是為了基於一些資料庫給HTML做渲染,那麼使用Node.js不會給你帶來任何利益好處。NoSQL + Node.js + 各種時髦詞 假如你的下一個應用程式的系統架構讀起來像NoSQL的配料菜譜,請花點時間閱讀下面的內容。Redis,CouchDB,MongoDB,Riak,Casandra等這些看起來似乎很誘人,同樣令人難以抗拒。如果你正在使用Node.js,那麼就不應該附加上一些你完全不瞭解的技術。當然,也有選擇一個文檔資料庫合理使用的案例。但是如果你想開發一個商業項目,請堅持保守的資料庫技術(比如Postgres 或者 MySQL)或許能滿足你的需求。出色的使用案例JSON APIs建立一個輕量級的REST / JSON API這確實是Node.js的一大亮點。如果需要封裝其他的資料來源(如資料庫)或者Web伺服器通過JSON介面讓他們暴露出來,那麼將非阻塞I/O模組與JavaScript結合在一起是個不錯的選擇。單一的頁面應用如果你打算寫一個AJAX單一的頁面應用(如Gmail),Node.js非常適合。在極短的回應時間內獲得更多的請求數,在用戶端和伺服器之間共用資料,為現代Web應用程式在用戶端上做大量的處理,Node.js都能滿足你的需求。Unix工具 Shelling out to unix tools目前Node.js還很年幼,它正試圖為自己重新發明各類軟體。不過更好的辦法是深入到現有的廣闊的命令列工具世界裡。Node可以把這些成千上萬的子進程以stream的方式輸出,這也使它成為企業的理想選擇。資料流Streaming data傳統的Web棧將http請求和響應作為元事件處理。然而,他們是流動的,許多非常棒的Node.js應用程式正是利用這一優點建立的。這裡有一個非常棒的案例,當進行即時解析上傳檔案時,還可以在不同的資料層之間建立代理。軟體即時應用利用Node.js你可以輕鬆開發軟體即時系統。比如Twitter、聊天工具,體彩或者即時通訊網路介面。但是,值得注意的是,因為JavaScript是一個動態......餘下全文>>