在衆多高級編程語言或運行平臺中,Node是首個將異步做爲主要編程方式和設計理念。前端
Node的基調:異步I/O、事件驅動和單線程。編程
Nginx採用純C編寫。後端
Nginx具有面向客戶端鏈接的強大能力,但受限於各類同步的編程語言。數組
Node既能夠做爲服務器去處理客戶端的大量併發請求,也能夠做爲客戶端面向網絡中的各個應用進行併發請求。瀏覽器
前端經過異步能夠消除掉UI阻塞的現象,可是前端獲取資源的速度也取決於後端的響應速度。服務器
I/O是昂貴的,分佈式I/O是更昂貴的。網絡
只有後端可以快速響應資源,才能讓前端的體驗變好。多線程
假設業務場景中有一組互不相關的任務須要執行,主流的解決方案有:併發
添加硬件資源是一種提高服務質量的方式,但並非惟一的方式。異步
單線程同步編程模型會因阻塞I/O致使硬件資源得不到更優的使用;
多線程編程模型也由於編程中的死鎖、狀態同步等問題讓人詬病。
Node的解決方案:
爲了彌補單線程沒法利用多核CPU的缺點,Node提供了相似前端瀏覽器中Web Works的子進程,該子進程能夠經過工做進程高效地利用CPU和I/O。
操做系統內核對I/O只有兩種方式:
阻塞I/O的特色:
阻塞I/O形成了CPU等待I/O,浪費等待時間,CPU的處理能力不能獲得充分利用。爲了提升性能,內核提供了非阻塞I/O。非阻塞I/O和阻塞I/O的差異爲調用以後會當即返回。
非阻塞I/O的缺點:
因爲完整的I/O並無完成,當即返回的並非業務層指望的數據,僅僅是當前調用的狀態。爲了獲取完整的數據,應用程序須要重複調用I/O操做來確認是否完成。(輪詢)
輪詢:
輪詢對於應用程序而言只能算是一種同步。
指望的完美的異步I/O應該是應用程序發起非阻塞調用,無須經過遍歷或者事件喚醒等方式輪詢,能夠直接處理下一個任務,只需在I/O完成後經過信號或回調將數據傳遞給應用程序。
多線程的異步I/O:
經過讓部分線程進行阻塞I/O或者非阻塞I/O加輪詢技術連完成數據獲取,讓一個進程進行計算處理,經過線程之間的通訊將I/O獲得的數據進行傳遞,實現異步I/O。
Windows的IOCP:
調用異步方法,等待I/O完成以後的通知,執行回調,用戶無須考慮輪詢,但內部是線程池的原理,不一樣之處在於這些線程池由系統內核接手管理。
Node的libuv:
Node提供了libuv做爲抽象封裝層,使得全部平臺兼容性的判斷都由這層來判斷,並保證上層的Node與下層的自定義的線程池及ICOP之間各類獨立。
完成整個異步I/O環節的有事件循環、觀察者和請求對象等。
事件循環:
觀察者:
每一個事件循環有一個或者多個觀察者,而判斷是否有事件要處理的過程就是 向這些觀察者詢問是否有要處理的事件。
在Node中,事件主要來源於網絡請求、文件I/O等這些事件對應的觀察者都有文件I/O觀察者、網絡I/O觀察者。
觀察者將事件進行了分類,事件循環是個典型的生產者/消費者模型。異步I/O、網絡請求等是事件的生產者,源源不斷爲Node提供不一樣類型的事件,這些事件被傳遞到對應觀察者,事件循環從觀察者中提取事件並處理。
請求對象是異步I/O過程當中的重要中間產物,全部的狀態都保存在這個對象中,包括送入線程池等待執行以及I/O操做完畢後的回調處理。
I/O觀察者回調函數的行爲就是取出請求對象的result屬性做爲參數,取出oncomplete_sym屬性做爲方法,而後調用執行,以達到調用JavaScript中傳入的回調函數的目的。
整個I/O的流程:
事件循環、觀察者、請求對象、I/O線程池這四者共同構成了Node異步I/O模型的基本要素。
非I/O的異步API:setTimeout()、setInerval()、process.nextTick()和setImmediate()。
setTimeout()和setInerval()與瀏覽器中的API是一致的,分別用於單次和屢次定時執行任務。
它們的實現原理和異步I/O類似,只是不須要I/O線程池的參與。調用setTimeout()或者setInerval()建立的定時器會被插入到定時器觀察者內部的一個紅黑樹中。每次Tick執行時,會從該紅黑樹中迭代定時器對象,檢查是否超過定時時間,若是超過,就造成一個事件,它的回調函數將當即執行。
定時器的缺點:
定時器並不是是精確的。儘管事件循環十分快,可是若是某一次循環佔用的時間較多,那麼下次循環時,它也許超時好久了。
每次調用process.nextTick()方法,只會將回調函數放入隊列中,在下一輪時取出執行。定時器中採用紅黑樹的操做時間複雜度爲O(lg(n)) ,nextTick () 的時間複雜度爲 O(1)。相比之下,process.nextTick()更加高效。
setImmediate()方法和process.nextTick()方法類似,都是將回調函數延遲執行。
可是,process.nextTick()中的回調函數執行的優先級要高於setImmediate()。
緣由在於事件循環對觀察者的檢查是有前後順序的,process.nextTick()屬於idle觀察者,setImmediate()屬於check觀察者。在每一輪循環檢查中,idle觀察者先於I/O觀察者,I/O觀察者先於check觀察者。
在具體實現上,process.nextTick()的回調函數保存在一個數組中,setImmediate()的結果保存在鏈表中;
在行爲上,process.nextTick()在每輪循環中會將數組中的回調函數所有執行完,而setImmediate()在每輪循壞中執行鏈表的每個回調函數。
事件驅動的實質:
經過主循環加事件觸發的方式來運行程序。
利用Node構建Web服務器流程圖:
服務器模型對比:
Node高性能的緣由: