[gist]爲何事件驅動服務器這麼火

from http://oyanglul.usphp

本文基本上這爲兩篇文章的翻譯和整合 -- Scalable networking And Why are event-driven server so greathtml

OPPC模型瓶頸

傳統服務器模型如Apache爲每個請求生成一個子進程。當用戶鏈接到服務器的一個子進程就產生,並處理鏈接。每一個鏈接得到一個單獨的線程和子進程。當用戶請求數據返回時,子進程開始等待數據庫操做返回。若是此時另外一個用戶也請求返回數據,這時就產生了阻塞。node

這種模式在很是小的工做負荷是表現良好,當請求的數量變得太大是服務器會壓力過於巨大。 當Apache達到進程的最大數量,全部進程都變得緩慢。每一個請求都有本身的線程,若是服務代碼使用PHP編寫時,每一個進程所須要的內存量是至關大的[1]。nginx

fork()操做延時

事實上,基於OPPC的網絡並不如想象中的高效。首先新建進程的性能很大程度上依賴於操做系統對fork()的實現,然而不一樣操做系統的處理並不是都理想。如圖爲各操做系統fork()的延遲時間對比。git

操做系統fork操做只是簡單的拷貝分頁映射。動態連接爲共享庫和全局偏移表中的ELF(Executable and Linking Format)部分建立太多的分頁映射。雖然靜態的連接fork會是的性能大幅度提高,可是延時依然不樂觀。github

圖 1 fork延時算法

進程調度

Linux每10毫秒(Alpha是1毫秒,該值爲已編譯常量)中斷一次在運行態的進程,查看是否要切換別的進程執行。進程調度的任務就是決定下一個應該執行的進程,而其難度就在於如何公平的分配CPU資源。一個好的調度算法應該給每個進程都分享公平的CPU資源,並且不該該出現飢餓進程。數據庫

Unix系統採用多級反饋隊列調度算法。使用多個不一樣優先級的就緒隊列,使用Heap保持隊列按優先級順序排序。Linux 2.6版本提供了一個複雜度O(1)的調度算法,將進程調度延時降至最小。可是進程調度的頻率是100Hz,意味着10毫秒會停止一個進程而判斷是否須要切換到另外一個進程。若是切換過多,會讓CPU忙於切換,致使下降吞吐量。api

內存佔用與線程

  建立多進程會帶來另一個問題:內存消耗。瀏覽器

每個建立的進程都會佔用內存,在Linux 2.6中的測試結果,400個左右的鏈接後fork()的性能要超過pthread_create()的性能。IBM對Linux作過優化後,一個進程能夠處理10萬個鏈接。fork()在每個鏈接時都fork()一次成本過高,多線程在於須要考慮線程安全(thread-safe)與死鎖(deadlock),以及內存泄露問題這些問題。   

可靠性

該模型具備可靠性問題。一個配置不當的服務器,很容易遭受拒絕服務攻擊(DoS)。當大量併發請求的服務器資源時,負載均衡配置不當時服務器會很快耗盡源而奔潰。

同步阻塞 I/O

在這個模型中,應用程序執行一個系統調用,這會致使應用程序阻塞。這意味着應用程序會一直阻塞,直到系統調用完成爲止(數據傳輸完成或發生錯誤)。調用應用程序處於一種再也不佔用CPU,而只是簡單等待響應的狀態,可是該進程依然佔用着資源。當大量併發I/O請求到達時,則會產生I/O阻塞,形成服務器瓶頸。

事件驅動模型服務器

經過上訴分析與實驗說明,事實上,操做系統並非設計來處理服務器工做負載。傳統的線程模型是基於運行應用程序是的一些密集型操做的須要。 操做系統的設計是讓用戶執行的多線程程序,使後臺文件寫入和UI操做同時進行,而並非設計於處理大量併發請求鏈接。

Fork和多線程是至關費資源的操做,建立線程須要分配一個全新的內存堆棧。此外,上下文切換也是一項開銷的,CPU調度模型是並不太適合一個傳統的Web服務器。

所以,OPPC模型面臨着多進程多線程的延遲已經內存消耗的問題。要用OPPC模型解決C10K問題顯得十分複雜。

爲解決C10K問題,一些新的服務器呈現出來。下列是解決C10K問題的Web服務器:

  • nginx:一個基於事件驅動的處理請求架構反向代理服務器。
  • Cherokee:Twitter使用的開源Web服務器。
  • Tornado:一個Python語言實現的非阻塞式Web服務器框架。Facebook的FriendFeed模塊使用此框架完成。
  • Node.js:異步非阻塞Web服務器,運行於Google V8 JavaScript引擎。

顯然以上解決C10K問題的服務器都有着共同特色:事件驅動,異步非阻塞技術。

因爲網絡負載工做包括大量的等待。好比 Apache服務器,產生大量的子進程,須要消耗大量內存。但大多數子進程佔用大量內存資源卻只是在等待一個阻塞任務結束。因爲這一特色,新模型拋棄了對每一個請求生成子進程的想法。全部的請求和事物操做只使用一個單獨的線程管理,此線程被稱之爲事件循環。事件循環將異步的管理全部用戶鏈接與文件存儲或數據庫服務器。當請求到達時,使用poll或者select喚醒操做系統對其請求作相應處理。解決了不少問題。這樣以來處理的併發請求再也不是牢牢圍繞在阻塞資源。固然,這樣也有必定的開銷,如保持一個始終打開的TCP鏈接的列表,但內存並不會因爲大量併發請求而急速上升,由於這個列表只佔內存堆上很小的一部分。Node.js和Nginx的都用這種方法來構建應用程序的規模超級大的鏈接數。一切操做都由一個事件循環管理,並很好地處理多個鏈接[4](圖3)。

目前最爲流行的事件驅動的異步非阻塞式I/O的Web服務器Node.js,稱其會在內存佔用上更爲高效,並且因爲不是傳統OPPC模式,也不用擔憂死鎖。Node.js沒有函數直接執行I/O操做[5],所以也不會產生阻塞[6]。

圖 3 事件驅動模型

圖 4 傳統OPPC模型

本文對目前應用最廣的傳統模型的Apache+PHP服務器,與兩個流行事件驅動模型服務器Node.js,tornado,進行了壓力測試。使用Apache bench對三個服務器分別進行每次1000個併發請求,總共100000次請求測試。分別監測和記錄了三個服務器的內存與CPU佔用狀況。

實驗環境爲Ubuntu 11.10,Intel Celeron 1.86GHzCPU,內存1G(oh no,教研室的古董機子)。服務器分別爲Apache2+PHP5.5,Node.js0.8.6和Tornado2.4.1。

{% include_code Nodejs nodeTest.sh %}

{% include_code Tornado tornadoTest.sh %}

{% include_code PHP phpTest.sh %}

圖4 內存佔用

圖 5 CPU佔用

在這個1000併發請求的壓力測試中能夠看到,基於事件驅動的Node.js與Tornado都比傳統OPPC模型的Apache服務器要快。固然Node.js的性能也離不開其運行於Google V8引擎上的緣由。兩個事件驅動模型服務器平均每秒處理的請求數爲Apache服務器的一倍,而內存下降了一半。圖2顯示事件驅動模型服務器會佔用更高的CPU,這說明這種模型雖然是單線程運行,可是能更高效的利用CPU處理更多的併發請求。

存在的不足

事件循環並不能解決一切問題[2]。特別是在Node.js的有一些缺陷。Node.js的最明顯的遺漏是多線程的實現。事件驅動技術彷佛應該都是多線程進行的,如大多數事件驅動GUI框架。理論上來講,事件之間應該是相互獨立的關係,所以並行化應該並不難實現。

雖然理論上是這樣,但一些技術上的緣由使得Node.js難以實現多線程。Node.js運行與Google的V8 Javascript引擎上[12]。V8引擎是一個高性能的JavaScript引擎,但它並無設計爲多線程。由於它本來爲Google Chrome瀏覽器Javascript引擎,瀏覽器中Javascropt在一個單線程上運行。所以添加多線程,將是很是艱難的,底層架構並不是爲服務器而設計。

將來

隨着nginx這樣的反向代理服務器的發展,可讓獨立運行的實例之間的負載均衡,Node.js的做者提出對解決多線程缺陷的最好的辦法是使用fork子進程,利用負載均衡來達到服務器併發任務處理。 這種解決方案彷佛像是要掩蓋其實現上的缺陷。但事件驅動模型倡導一個邏輯服務器應該應該能在單核CPU下表現得最優,以及佔用更少的內存。與此相反,Apache的最初目的是以一切可利用的資源爲代價充分高效管理併發和線程。事件驅動模型服務器避開了這種繁瑣的設計而用最簡潔高效的方式實現了可擴展性良好的服務器。

單線程的也正符合雲計算的平臺的計算單位。很明顯,一個單一的雲實例,很是適合運行一個單一的Node.js的服務器,並使用負載均衡橫向擴展。

事件驅動模型的出現,是爲了解決傳統服務器與網絡工做負載的需求的不匹配,實現高度可伸縮服務器,並下降內存開銷。事情驅動模型更改了鏈接到服務器的方式。全部的鏈接都由事件循環管理,每一個鏈接觸發一個在事件循環進程中運行的事件,而不是爲每一個鏈接生成一個新的 OS 線程,併爲其分配一些配套內存。所以不用擔憂出現死鎖,並且不會直接調用阻塞資源,而採用異步的方式來實現非阻塞式I/O。經過事件驅動模型是的在相同配置的服務器能接受更多的併發請求,實現可伸縮的服務器。

[1] Suzumura, T.; Trent, S.; Tatsubori, M.; Tozawa, A.; Onodera, T.; , "Performance Comparison of Web Service Engines in PHP, Java and C," Web Services, 2008. ICWS '08. IEEE International Conference on , vol., no., pp.385-392, 23-26 Sept. 2008

[2] Von Behren, Rob, Jeremy Condit, and Eric Brewer. "Why events are a bad idea (for high-concurrency servers)." Proceedings of the 9th conference on Hot Topics in Operating Systems. Vol. 9. 2003.

[3]Welsh, Matt. "The staged event-driven architecture for highly-concurrent server applications." University of California, Berkeley (2000).

[4] Griffin, L., Ryan, K., de Leastar, E., Botvich, D., "Scaling Instant Messaging communication services: A comparison of blocking and non-blocking techniques", Computers and Communications (ISCC), 2011 IEEE Symposium on, On page(s): 550 - 557

[5] Tilkov, Stefan, and Steve Vinoski. "Node. js: Using JavaScript to build high-performance network programs." Internet Computing, IEEE 14.6 (2010): 80-83.

[6] Deitcher, Avi. "Simplicity and performance: JavaScript on the server." Linux Journal 2011.204 (2011): 3.

[11] W. Stevens. TCP/IP Illustrated Volume 3. Addison-Wesley, Reading, MA, 1996. [12]: http://code.google.com/apis/v8/design.html accessed

相關文章
相關標籤/搜索