Apache Nginx lighttpd HAProx Litespeed 緩衝原理解析fastcgi性能

保留JuuJo Blog 地址,以後多去學習!

原文地址:http://www.juujo.com/blog/index.php/archives/301

由於最近在忙於web server的開發,對於靜態部分跟動態部分的交互一直遲遲未定,緩衝區大小也一直很頭疼,看了下面的這篇文章覺得不錯,我還是這樣覺得,簡單的就是最好 的,但並不意味着所有處理都用一種方式,正如我在靜態輸出的socket buffer上面一樣,我是根據請求內容的大小來決定緩衝區分配的,即使這樣作在系統內部會形成一次內存拷貝(socket回去處理),但是相對於網絡的 延遲速度快多了,所以我覺得還是根據不同的內容和大小決定緩衝區大小,既不是lighttpd的照單全收,也不是nginx等的8K繡花,對於接收,首先 分配一個2k-8K的緩衝區去收,西一頭部接收完畢了就可以知道剩下的contentlength,比如內容在8K-64K之間的用32K,64K-無窮 大K之間的用64K,這樣就可以用進程內的一次內存拷貝換取網絡的延遲問題,對於fastcgi的支持也會更好,下面是原文:
RoR的部署方案可謂五花八門,有Apache/Fastcgi方式的,有Nginx/Mongrel方式的,還有lighttpd/Fastcgi方 式,也有人使用HAProxy/Mongrel,各種部署方式都是衆說紛紜,讓人搞不清楚哪種方式更好一些。我的這篇文章就是希望結合我們運營 JavaEye網站一年多以來的經驗(通過統計Rails的production.log,JavaEye網站目前每天處理超過70萬200 OK狀態的Ruby動態請求,應該是國內目前負載量最大的RoR應用了),爲大家剖析RoR部署方案的優劣,幫助大家選擇適合自己生產環境的RoR部署方 式。

在討論部署方案之前,先讓我們看一下RoR網站部署的簡單架構:

瀏覽器的HTTP訪問請求首先達到Web服務器,充當Web服務器的一般是Lighttpd/Apache/Nginx,如果訪問請求包含靜態資 源,那麼Web服務器就會直接從本地硬盤讀取靜態資源文件,例如圖片,JavaScript,CSS等等,返回給客戶端瀏覽器;如果訪問請求是動態請求, 那麼Web服務器把URL請求轉發到後端的FastCGI/Mongrel來處理,等到FastCGI/Mongrel處理完請求,將生成的頁面數據返回 給Web服務器,最後Web服務器把頁面數據發送到客戶端的瀏覽器。

從RoR的部署方式來看,主要由前端的Web服務器和後端的應用服務器構成:前端的Web服務器可以使用Apache,Lighttpd,Nginx和Litespeed,後端的應用服務器可以使用FastCGI和Mongrel,下面我們分門別類的介紹和剖析:

一、介紹Web服務器

Web服務器的主要作用有兩點:一是處理靜態資源,二是將動態請求分發到後端應用服務器,然後接收後端應用服務器生成的頁面數據,將其返回瀏覽器,充當了一個信息溝通的橋樑作用,在本文當中我們重點分析後者的作用。

1、Apache 2.2

Apache是全球互聯網使用最廣泛的Web服務器,但在處理靜態資源文件上卻不是性能最優秀的Web服務器,不過一般情況下,靜態資源的訪問並不是RoR網站的瓶頸,因此也不必過於在意這一點。

Apache 2.2既支持HTTP Proxy方式連接後端的Mongrel應用服務器,也可以通過mod_fastcgi/mod_fcgid來連接FastCGI應用服務器:當以 HTTP Proxy方式連接Mongrel的時候,Apache接收Mongrel返回的頁面數據的buffer size最大隻能開到8KB(默認是4KB或者8KB),因此當頁面數據超過8KB的時候,可能需要Apache和Mongrel之間發生多次交互;當以 mod_fastcgi方式連接FastCGI應用服務器的時候,接收返回數據的Buffer size仍然只有8KB而已,如果使用mod_fcgid,那麼buffer size爲64KB,有了很大的改善。

2、Nginx

Nginx是俄國人出品的輕量級Web服務器,在處理靜態資源方面,據說性能還略微超過Lighttpd的10%,而且資源消耗更小。

Nginx內置了良好的HTTP Proxy和FastCGI支持,因此即可以連接Mongrel,也可以連接FastCGI服務器,在這兩種情況下,Nginx默認的接收應用服務器返回 數據的Buffer Size也只有區區的8KB,但是你可以自行設置更大Buffer Size。

3、Lighttpd

Lighttpd是全球互聯網排名第五的Web服務器,也是近兩年來上升最快的Web服務器,特別是很受一些著名Web 2.0大網站的歡迎,例如wikipedia的某些服務器,youtube的視頻服務器,在國內,豆瓣網站和JavaEye網站都是Lighttpd的絕 對擁護者。在處理靜態資源方面,Lighttpd性能遠遠超過Apache。

Lighttpd既支持HTTP Proxy連接Mongrel,也支持FastCGI方式,但是Lighttpd的FastCGI支持在所有流行的Web服務器當中可能是最優秀的,所以 用FastCGI的網站都很喜歡Lighttpd。Lighttpd在接收後端應用服務器返回數據的方式上和Apache/Nginx有非常大的區別:

Apache/Nginx是針對每個應用服務器連接分配固定Size的Buffer,而且默認只開8KB,這個Size對於現在網頁動輒 50-100KB的情況來說,顯得過於保守,如果應用服務器的返回數據無法一次填滿Web服務器的Buffer,那麼就會導致應用服務器和Web服務器之 間多次數據傳輸,這對於RoR網站的性能會造成一些相關的影響,我們在後面會詳細的分析。

Lighttpd並不針對應用服務器的每個連接分配固定的Buffer,而是儘可能的把應用服務器返回的數據一次性接收下來,因此無論應用服務器返回多大的數據量,Lighttpd都是照單全收,胃口非常驚人。

4、Litespeed

Litespeed是一個商業收費的Web服務器,靜態資源處理能力據它自己的評測數據比Lighttpd略高。Litespeed也同時支持 HTTP Proxy連接Mongrel和FastCGI連接應用服務器。此外Litespeed專門爲單機運行的RoR開發了一個lsapi協議,號稱性能最好的 RoR通訊協議,比HTTP Proxy和FastCGI都要好。但是lsapi的運行方式有很大缺陷:因爲lsapi不是web server啓動的時候啓動固定數目的ruby進程,而是根據請求繁忙程度,動態創建和銷燬ruby進程,貌似節省資源,實則留下很大的黑客攻擊漏洞。只 要黑客瞬時發起大量動態請求,就會讓服務器忙於創建ruby進程而導致CPU資源耗盡,失去響應。

由於Litespeed在運行RoR方面並沒有表現出比Lighttpd優越之處,而且還是收費軟件,企業版本售價在雙核CPU上面每年收費 499美元,並且也不開源,因此我們就不再把關注點放在Litespeed上面。當然Litespeed收費也不是白收的,它提供了非常好用的基於Web 的服務器管理界面,以及非常多的安全性方面的設置參數。

5、HAProxy

HAProxy並不是一個Web服務器,他既不能處理靜態資源,也不能充當瀏覽器和應用服務器之間的緩衝橋樑,他只是充當了一個請求分發的軟件網關 作用。ThoughtWorks公司的RubyWorks選擇使用HAProxy + Mongrel Cluster的方式來部署RoR應用,不能不說是一個愚蠢的方案。這種方案其實相當於把n個Mongrel應用服務器捆綁起來,直接充當Web服務器, 而Mongrel畢竟是一個Ruby寫的服務器,無論是網絡IO能力,還是靜態資源的處理速度,無法和真正的Web服務器相提並論,讓Mongrel直接 處理靜態資源和調度網絡IO,會造成服務器資源毫無必要的極大開銷,因此HAProxy也不在我們的考慮之列。

二、分析應用服務器的處理方式

無論是Mongrel還是FastCGI,都能夠良好的運行Rails服務器,但是他們在和Web服務器之間的數據傳輸方式上存在一些差別,而正是這些差別,對部署方式有重大的影響:

1、Mongrel

Mongrel本身可以直接充當Web服務器,但在這種情況下性能並不會好。因爲Mongrel只有HTTP協議的解析部分是用C語言編寫的,其餘 所有代碼都是純Ruby的。在處理靜態資源下載上面,Mongrel的實現方式非常低效率,他只是簡單的以16KB爲單位,依次讀入文件內容,再寫出到網 絡Socket端口,其性能遠遠比不上傳統的Web服務器調用操作系統的read()和write()庫實現的靜態文件下載速度,如果和現代Web服務器 實現的sendfile方式的“零拷貝”下載相比,簡直就是望塵莫及。

Mongrel使用了Ruby的用戶線程機制來實現多線程併發,並且使用了一個fastthread補丁,改善了Ruby用戶線程的同步互斥鎖問 題。但是Ruby並不是本地線程,我們也不要對Mongrel的網絡IO負載能力抱有什麼不切實際的幻想。同時Rails本身也不是線程安全的,因此 Mongrel在執行Rails代碼的過程中,完全是加鎖的狀態,那和單進程其實也沒有太大差別。

因此,當我們使用Mongrel的時候,一般會在前端放置Web服務器,通過HTTP Proxy方式把請求轉發給後端的Mongrel應用服務器。在這種情況下,Mongrel只處理動態請求,在運行Rails框架生成頁面數據之後,把數 據返回給Web服務器就可以了。但是在這種部署方案下,有一個很重要的細節被我們忽視了,Mongrel運行Rails生成的頁面數據是怎麼返回給Web 服務器的呢?通過仔細鑽研源代碼我們可以搞清楚Mongrel處理Rails請求的細節:

1) Mongrel接收到請求以後,啓動一個ruby線程解析請求信息
2) 加鎖,調用Rails Dispatcher啓動Rails框架
3) Rails處理完畢,創建一個StringIO對象,把Rails生成的頁面數據寫入到StringIO中
4) 解鎖,把StringIO的數據flush到Web服務器

這個StringIO對象其實很重要!它充當了一個輸出緩衝區的作用,我們設想一下,當Mongrel作爲獨立的Web服務器的時候,如果 Rails生成的頁面比較大,而客戶端瀏覽器下載頁面的速度又比較慢,假設沒有這個StringIO對象,會發生什麼問題? Rails線程在執行render方法的時候就會被掛住!同步互斥鎖沒有解鎖,Mongrel再也無法處理下一個動態請求了。

當Mongrel僅僅作爲應用服務器的時候,這個StringIO仍然很重要,爲什麼?我們前面提到過了,Apache/Nginx的接收緩衝區都 只開了8KB,如果頁面比較大,Mongrel就沒有辦法一次性把數據全部推給Web服務器,必須等到Web服務器把接收緩衝區的8K數據推到客戶瀏覽器 端以後,清空緩衝區,才能接收下一個8KB的數據。這種情況下,Mongrel必須和Web服務器之間進行多次數據傳輸,才能完成整個Web響應的過程, 顯然沒有一次性把頁面數據全部推給Web服務器快。如果Web服務器使用Lighttpd的話,情況會不一樣。當Mongrel把StringIO的數據 flush出去的時候,Lighttpd是一次性全部接收下來了,不需要多次交互,因此Lighttpd+Mongrel的RoR網站的實際速度要快於 Apache/Nginx+Mongel。

Mongrel使用StringIO對象緩存輸出結果,在某些特殊的情況下會帶來很大的安全隱憂。我們假設使用服務器端程序控制帶權限的文件下載, 某用戶下載的是一個100MB的文件,該用戶使用了多線程下載工具,他開了10個線程併發下載,那麼每個線程Mongrel在響應之後,都會把整個文件讀 入到內存的StringIO對象當中,所以總共會創建出來10個StringIO對象保存10份文件內容,所以Mongrel的內存會一下暴漲到 1GB以上。而且最可怕的是,即使當用戶下載結束以後,Mongrel的內存都不會迅速回落,而是一直保持如此高的內存佔用,這是因爲Ruby的GC機制 不好,不能夠及時進行垃圾回收。

也許你會覺得不太可能下載100MB那麼大的附件,但是以JavaEye網站爲例,圈子的共享文件最大允許10MB,只要用戶在多臺機器上面,每臺 機器開100個線程下載圈子共享文件,每個Mongrel的內存佔用都會立刻超過1GB,用不了幾分鐘,服務器的物理內存就會被耗盡,網站失去響應。這個 缺陷非常容易被別有用心的黑客利用,攻擊網站。這也是JavaEye網站爲什麼始終不用mongrel的原因之一。

通過上面的剖析,我們知道Mongrel在使用Lighttpd的時候,可以達到最快的RoR執行速度,但是Lighttpd當前的1.4.18 版本的HTTP Proxy的負載均衡和故障切換功能有一些bug,因此一般很少有人會使用這種方式。大多數人都會採用Mongrel搭配Apache2.2或者 Nginx,但是正如我們上面做分析的那樣,Apache/Nginx的Buffer Size實在是一個很討厭的限制,特別是Apache只能最大開8KB的Buffer,因此我建議使用Nginx搭配Mongrel,並且把Nginx的 Proxy Buffer Size設置的大一些,比如說設置爲64KB,以保證大多數頁面輸出結果可以一次性flush到Web服務器去。

2、FastCGI

很多人對FastCGI談虎色變,彷彿FastCGI就是內存泄漏,性能故障的罪魁禍首,又或者嫌棄FastCGI太古老了,已經被淘汰掉的技術 了,其實這是一個很大的誤解。FastCGI本質上只是一種進程間通訊的協議,雖然是一個比較古老的協議,但是還是比HTTP協議年輕多了,HTTP協議 不是照樣現在很流行嗎?

在PHP/ASP/JSP流行之前,FastCGI曾經非常普及,只不過那個時代的FastCGI程序是用C語言編寫的,寫起來太費勁,而PHP /ASP/JSP相比之下,寫起來就太簡單了,所以FastCGI就漸漸被丟到了歷史的故紙堆裏面。但是最近兩年來,由於Ruby和Python的快速 Web開發框架的強勢崛起,FastCGI彷彿又鹹魚翻身了。

當我們以FastCGI方式運行Rails應用服務器的時候,每個FastCGI進程都是單線程運行的,考慮到Rails本身不是線程安全的,所以 和Mongrel運行Rails的實際效果是一樣的,都是每個進程只能跑一個Rails實例。但是FastCGI在Rails生成頁面數據返回給Web 服務器的方式和Mongrel截然不同:

前面我們說到Mongrel自己開了輸出緩衝區,而FastCGI則完全不開任何緩衝區,當Rails執行render方法的時候,FastCGI 實際執行的是FCGI::Stream.write方法調用,直接把數據寫給Web服務器了。此時如果Web服務器是 Apache/Nginx,會發生什麼?

如果我們使用mod_fastcgi模塊,那麼Apache的接收緩衝區就是8KB;
如果我們使用mod_fcgid模塊,那麼Apache的接收緩衝區就是64KB;(mod_fcgid是中國人開發的取代mod_fastcgi的開源項目,在Apache社區很受歡迎,誰敢說中國人只是開源“消費”國?)
如果我們使用Nginx服務器,那麼默認的接收緩衝區就是8KB,但是可以改得更大;

如果頁面數據比較大,超過8KB,會怎麼樣? FastCGI進程被掛在render方法上!必須等到Web服務器的緩衝區清空,把頁面數據全部接收下來以後,FastCGI進程才能結束本次 Rails調用,處理下一個請求!所以千萬別用Apache/Nginx搭配FastCGI應用服務器,否則你的RoR應用會死的很難看。根據我個人的測 試數據表明,同樣的測試負載,Apache搭配70個FastCGI進程掛掉,但是Lighttpd搭配30個FastCGI進程輕鬆跑完!

當FastCGI搭配Lighttpd的時候,我們知道Lighttpd會一次性照單全收FastCGI送過來的頁面數據,所以FastCGI進程 並不會被掛住。如果我們對比一下Lighttpd搭配Mongrel和FastCGI會發現,Lighttpd搭配FastCGI性能最好,爲什麼呢?

Mongrel首先自己會用StringIO緩衝頁面數據,然後推送給Lighttpd以後,Lighttpd也在內存當中緩衝了一份頁面數據,造 成了毫無必要的double buffer的開銷。這自然不如FastCGI不做任何緩衝,直接推給Lighttpd性能來得高,內存消耗少了。

我們的方案分析到這裏,大家應該自己心裏有結論了,Lighttpd+FastCGI是性能最佳,服務器資源消耗最少的RoR部署方案,事實上目前 RoR網站部署使用最多最流行的也是Lighttpd+FastCGI方式,而JavaEye網站,自然也是這種方式的部署。因此我們可以對各種方案進行 一個性能優劣的排隊:

引用

Lighttpd+FastCGI > Lighttpd+Mongrel > Nginx+Mongrel > Apache+Mongrel > Ngignx+FastCGI > Apache+FastCGI

其中Lighttpd+FastCGI是性能最佳方案,而Apache+FastCGI是性能最差方案。

有些細心的同學可能會產生一個新的疑問?你說到底,之所以Lighttpd跑RoR性能最好,還是在於Lighttpd接收數據不限定緩衝區的大 小,而 Apache/Nginx限定了緩衝區大小所至。那爲什麼Nginx要限制呢?Lighttpd如果不限制的話,會不會導致Lighttpd內存爆掉?

Nginx限制Proxy Buffer Size其實也有道理,因爲Nginx並不是爲RoR量身打造的Web服務器,Nginx最廣泛的用途還是高負載大訪問量的代理服務器,在Nginx主要 的應用場合,如果不做這樣的限制,那Nginx端的資源消耗就相當高了,有可能會拖累所代理的服務速度。

Lighttpd主要用途之一就是提供高性能的FastCGI支持的Web服務器,所以必須爲FastCGI量身打造。Lighttpd端承擔的負 載越高,就越能有效的加快FastCGI執行速度。其實我們稍微心算一下,假設Lighttpd後面掛1000個FastCGI進程,每個 FastCGI進程同時送過來50KB的頁面數據,Lighttpd就是全部吃下來,也不過只消耗50MB的內存而已,而事實上1000個FastCGI 進程足以支撐每日上千萬的大網站了。

只有當我們使用服務器端程序控制大文件下載的時候,有可能造成Lighttpd內存暴漲,例如某個用戶使用100個線程併發下載JavaEye圈子 的共享文件,在沒有特殊處理的情況下,Lighttpd將全部吃下100個FastCGI進程送過來的10MB數據,就會立刻暴漲1GB的內存。這種情況 怎麼辦呢?其實我們也有辦法讓Lighttpd一點內存都不吃,請看我寫的另外一篇文章:RoR網站如何利用lighttpd的X-sendfile功能 提升文件下載性能

可能很多人看了我的文章,對結論覺得很詫異,既然Lighttpd+FastCGI這樣好,爲什麼那麼多人都推崇Mongrel,否定FastCGI呢?我想,不外乎幾個原因:

一、Lighttpd+FastCGI配置起來比較專業,而Mongrel配置簡單

儘管我當初第一次搭建Lighttpd+FastCGI環境沒費什麼周折,但是我觀察到非常多的Ruby程序員很難成功搭建一個 Lighttpd+FastCGI的環境出來,很多人連Lighttpd都無法獨立的運行起來。這也許是因爲很多程序員習慣了Windows開發環境,對 於Unix上面通過源代碼編譯安裝的方式過於陌生造成的。

而我從97年開始使用Unix,至今已有10年曆史,因此搭建這樣簡單的系統,對我來說不造成什麼障礙。

而Mongrel就簡單了,gem install mongrel安裝完畢,mongrel_rails start啓動,哪個人不會?畢竟絕大多數開發人員和部署人員不是高手,他們熟悉哪種方式,自然就會推崇哪種方式。

二、Mongrel可以獨立作爲Web服務器運行,開發環境和部署環境統一

一般來說,程序員肯定是儘量保持開發環境和部署環境的一致性,避免部署到生產環境出現不測的後果。既然在開發環境熟悉了Mongrel,當然更加願意在生產環境使用Mongrel,而不願意碰沒有接觸過的Lighttpd。

三、Mongrel支持HTTP協議,因此不論監控還是集成其他服務都比較簡單,容易玩出更多的花活。

HTTP協議要比FastCGI協議普及的多,因此通過HTTP方式的監控工具,羣集管理工具,集成其他服務的工具都是一抓一大把。而支持 FastCGI的第三方工具就少得可憐了。你要玩很多花活出來,用FastCGI的話,就難免得自己開發相應的工具,那當然不如使用Mongrel方便 啦。

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