天天看點

CentOS RoR應用性能優化的實戰經驗

CentOS RoR應用性能優化的實戰經驗

2010-02-23 16:17 佚名 csdn 我要評論(0) 字号:T | T

CentOS RoR應用性能優化的實戰經驗

随着網站負載的不斷增加,我們也在不斷嘗試和調整網站的性能,積累了不少第一手CentOS RoR應用性能優化的實 戰經驗。雖然我們并不是CentOS RoR性能優化的權威專家,我們所積累的經驗也許并不是最優實踐,但是作為國内最早涉足CentOS RoR商業營運的網際網路網站之一,我們非常 樂意分享和交流我們的實戰經驗,以幫助後來者節省必要的摸索時間。

AD:

CentOS RoR對于電腦使用的玩家的常用軟體,然後我就學習及深入的研究CentOS RoR,在這裡和大家一起探讨CentOS RoR的使用方法,希望對大家有用。JavaEye網站從2006年9月11日上線基于CentOS RoR的2.0版本開始,到現在已經運作了将近一年半了。在這一年半的時間裡,JavaEye網 站的每 日PV從最開始的5萬,緩慢增長到了現在的60萬。

随着網站負載的不斷增加,我們也在不斷嘗試和調整網站的性能,積累了不少第一手CentOS RoR應用性能優化的實戰經驗。雖然我們并不是CentOS RoR性能優化的權威專家,我們所積累的經驗也許并不是最優實踐,但是作為國内最早涉足CentOS RoR商業營運的網際網路網站之一,我們非常 樂意分享和交流我們的實戰經驗,以幫助後來者節省必要的摸索時間。

CentOS RoR驚人的開發速度恐怕是每個網際網路創業者都夢寐以求的,但是随着網站流量的不斷增大,可能大多數采用CentOS RoR的網站或遲或早會遇到CentOS RoR的性能 瓶頸,我的一個朋友capitian說過一句很有意思的話:“CentOS RoR應用做到後來,總有自己修改底層的沖動”。就我所了解和掌握的情況來看,很多CentOS RoR網 站都過早的遇到了性能瓶頸,一個很普遍的現象就是:CentOS RoR應用的CPU負載要遠遠高于資料庫的負載。

這是一個有點違背常理的現象,因為我們知道,硬碟IO 速度要比記憶體慢得多,是以一般Web應用的性能瓶頸往往會出現在資料庫IO上,是以優化資料庫通路,進行對象緩存是非常有效的性能優化手段。但是一旦應用 伺服器負載比資料庫還高的話,單純的對象緩存就無用武之地了。下面我們從幾個方面分别談一談如何進行CentOS RoR的性能優化:

應用的部署

CentOS RoR應用的部署包括作業系統,Web伺服器,應用伺服器和資料庫四個方面:

一、作業系統

1、發行版本

CentOS RoR适合于部署在Unix類作業系統上面,通常比較多的人使用RHEL/CentOS/Ubuntu,我們比較偏愛SuSE Linux,對于我們伺服器使用的AMD Opteron x86_64的CPU來說,SLES要比RHEL有更多的優化。另外應該盡量使用64位版本作業系統,以充分發揮x86_64 CPU的性能,并且x86_64的Linux很多Kernel參數也大很多,代價就是需要更多的實體記憶體。

2、檔案系統

Linux最常用的檔案系統是ext3,但我們使用的是Reiserfs檔案系統。Reiserfs在讀寫大量小檔案的目錄性能非常高,即使處理 目錄下面直接存放10萬個檔案,性能仍然不會下降。我們知道預設情況Rails會對每個浏覽器會話在硬碟生成session檔案,一個繁忙的網站,臨時文 件目錄下面有上萬乃至幾萬個session檔案是很常見的現象。對于這種目錄下面幾萬個小檔案的存取,reiserfs要比ext3性能高一個數量級。如 果希望對session檔案有更好的存取性能,可以把臨時目錄連結到Linux的記憶體檔案系統/dev/shm目錄下面,這樣實際上session檔案的 存取都是直接記憶體操作了,這種方式唯一的問題在于不能支援群集部署。如果你已經更新到了Rails2.0,可以采取把session儲存到Cookie裡 面的方式,既可以避免伺服器處理session的開銷,而且還支援群集部署,是大規模網站部署的首選方式。

3、核心的網絡參數調整

對于流量很大的網站來說,預設的Linux核心網絡參數偏小,是以如果你的網站流量非常大,或者上傳下載下傳大檔案比較多,可以針對性的調整核心網絡參數,擴大核心的TCP接收資料和發送資料的Buffer緩沖區大小,比方說:

引用

net.core.rmem_default=262144

net.core.wmem_default=262144

net.core.rmem_max=262144

net.core.wmem_max=262144

net.ipv4.tcp_rmem=4096 65536 524288

net.ipv4.tcp_wmem=4096 65536 524288

參數具體調整,可以Google相關的Linux核心參數的文檔,這裡不展開詳談。

二、Web伺服器

Web伺服器首選Lighttpd,因為Lighttpd在和後端的應用伺服器通訊方式上做了足夠的優化:當POST大資料量的時 候,Lighttpd在完整的接收用戶端浏覽器的資料之後,才會一次性發送給應用伺服器;同樣的,Lighttpd也是一次性把應用伺服器處理的頁面資料 全部接收,不設定Buffer Size的限制。是以Lighttpd能夠盡最大可能的減輕應用伺服器的負擔,減少應用伺服器用于處理資料傳輸的延遲,更加有效的利用應用伺服器資源。這 方面的詳細的論述請看:CentOS RoR部署方案深度剖析。

關于Lighttpd的安裝可以參考在Linux平台上安裝和配置Ruby on Rails詳解,這裡僅談Lighttpd的性能優化的幾個要點:

1、網絡IO排程方式

Linux Kernel 2.6支援sysepoll方式排程網絡IO,能夠處理極高的并發連接配接請求,Lighttpd可以通過配置檔案打開sysepoll支援:

server.event-handler = "linux-sysepoll"

2、網絡IO傳輸方式

Linux Kernel 2.6支援sendfile方式傳輸資料,Lighttpd可以通過配置檔案打開sendfile支援:

server.network-backend = "linux-sendfile"

此外Lighttpd還支援應用伺服器參與的檔案下載下傳控制X-sendfile,詳細的論述請看:CentOS RoR網站如何利用lighttpd的X-sendfile功能提升檔案下載下傳性能

3、檔案狀态緩存

Lighttpd通過stat()調用獲得檔案被修改的資訊,來決定當請求同一個靜态檔案資源的時候,是否需要再次讀取硬碟檔案。但是每次 stat()調用也有一定的開銷,Lighttpd支援通過Fam Server來減少stat調用。即每次當檔案被修改之後,Kernel會發送一個消息通知Fam Server,而Lighttpd會通過程序間通訊連接配接Fam Server,可以知道檔案是否被修改的資訊,不必再每次調用stat()。

server.stat-cache-engine = "fam"

4、限定POST Size

為了避免黑客惡意的攻擊伺服器,僞造超大Post資料包轟炸Web伺服器和應用伺服器,可以限制Request請求的大小,例如限制為10MB:

server.max-request-size = 10240

5、日志檔案

Lighttpd是單程序單線程的伺服器,排程網絡IO性能是極高的,但是在某些極端情況下,單程序伺服器也有風險,即一旦被某作業系統調用挂 住,整個伺服器就沒有辦法響應請求了。比方說伺服器其他程序導緻的IO WAIT很高,作業系統的buffer又不夠的時候,Lighttpd在大量的寫access log就有被挂住的可能性。是以如果Lighttpd日志對你的參考價值不大,可以考慮關閉掉。像JavaEye網站每天Lighttpd産生430萬條 log,對硬碟IO也是一個不小的負擔,既然已經開着Rais的production.log,那麼Lighttpd的access log沒什麼參考價值了,那就關掉它。

Lighttpd的性能優化請看其作者寫的文章:

http://trac.lighttpd.net/trac/wiki/Docs%3APerformance

三、應用伺服器

Ruby的應用伺服器可以使用FastCGI,或者Mongrel,如果我們使用Lighttpd的話,FastCGI是最好的搭配。

1、FastCGI和Lighttpd的通訊方式

如果FastCGI和Lighttpd是在同一台伺服器,那麼建議采用Unix Socket通訊,這種通訊方式比TCP要快一些,FastCGI可以通過Lighttpd自帶的spawn-fcgi指令行工具啟動,建立socket 檔案,而Lighttpd監聽socket檔案。如果兩者不在同一台伺服器,需要群集部署,那就必須采用TCP Socket通訊,方式是一樣的。

2、FastCGI程序應該開多少個合适?

Rails是單程序方式運作的,理論上來說,開幾個FastCGI程序,就隻能并發響應幾個請求。對于繁忙的網站來說,峰值期間每秒有幾十個動态 請求是很正常的事情,但實際上FastCGI程序并不需要開那麼多。這是因為前端的Web伺服器在處理使用者浏覽器連接配接,發送Request請求需要相當長 的時間,在FastCGI處理完請求釋放該連接配接以後,Web伺服器還需要相當長的時間才能把頁面資料完整的發送到用戶端浏覽器。使用者在點選一個連結以後, 等待1-2秒,頁面内容就顯示出來,這對使用者的感覺來說已經是非常快的了,而FastCGI用于處理該請求可能隻需要0.1秒,那麼一個FastCGI進 程雖然并不能夠真正的并發運作,但實際上的效果是他可以在1秒之内處理10個請求,讓10個使用者在同時通路網站的過程當中感覺不到明顯的延遲。

是以FastCGI需要開多少個,取決于你的網站峰值期間每秒有多少個使用者請求過來,而你的FastCGI又能夠以多快的速度處理請求。比方說你 的網站峰值期間每秒有50個動态請求,FastCGI在峰值期間處理每個請求需要0.2秒,那麼實際上你隻需要開10個FastCGI程序就足夠了,為了 應付突發的峰值請求,你可以在這個計算量上面增加一些餘量,比方說15-20個程序,肯定是綽綽有餘了。

關于FastCGI的性能優化,可以參考Lighttpd作者的文章,雖然他是針對PHP跑FastCGI寫的,但對CentOS RoR也有參考價值:

http://trac.lighttpd.net/trac/wiki/Docs%3APerformanceFastCGI

四、資料庫

JavaEye網站使用MySQL5.0.XX版本,資料庫引擎是InnoDB。關于MySQL資料庫的調優,推薦大家看MySQL Performance Blog, 作者是一個MySQL性能調優方面的專家,并且提供MySQL咨詢服務。他的部落格上面有很豐富的關于MySQL調優的文章和演講文稿,特别是關于 InnoDB方面,非常深入。JavaEye的資料庫調優就是根據他的InnoDB演講文稿來調整的,一般說來,有幾個需要調整的參數:

innodb_buffer_pool_size

這個參數很重要,越大越好,對于專用的資料庫伺服器一般建議開伺服器記憶體的50%以上。

query_cache_size

查詢緩存,對于查詢的性能提高有很大幫助,但不宜開得過大,查詢緩存的過期可能很頻繁,過大查詢緩存反而降低性能,增加伺服器開銷

innodb_flush_method = O_DIRECT

針對InnoDB的資料檔案,關閉作業系統的檔案緩沖,由于InnoDB自己有巨大的Buffer Pool,作業系統對檔案的讀寫緩沖功能反而會降低MySQL的InnoDB的IO性能。

最後針對資料庫的SQL優化來說有兩點原則:

1、對資料庫表要适當的建立索引

特别是出現在where查詢條件當中字段,和關聯查詢當中的外鍵,要高度注意。

2、盡量避免大表的全表掃描和資料庫的硬碟IO

查詢比較慢的SQL要explain一下,看看是否發生了全表掃描,采取各種措施減少或者避免大表的全表掃描問題,例如拆分表等等。

最後針對MySQL資料庫運作情況,我們可以用show status; 和 show innodb status"G 來監測。

Rails應用程式的優化

Rails應用程式優化包括ruby解析器的優化,緩存的使用,以及應用代碼級别的優化。Stefans Kaes曾經在Railsconf 2006有一個Rails應用程式優化的演講,他的演講PPT是極好的Rails性能優化指南,可以在這裡下 載:http://www.javaeye.com/topic/24508。他還編寫了一個用于Rails性能測試的軟體包RailsBench,大家 可以參考。由于Stefans Kaes的代碼優化文檔已經寫的非常詳細了,是以我就不在一一複述,隻提出幾點對性能影響比較大的方面:

一、ruby解析器的優化

ruby的解析器性能是很糟糕的,ruby早期的主要用途是取代perl寫批量處理的腳本的,并不是為伺服器應用編寫的,是以在記憶體配置設定政策上非 常不适合伺服器應用。Stefans Kaes編寫了一個ruby GC的更新檔檔案,在railsbench下載下傳包裡面提供了。雖然目前Railsbench提供的GC更新檔隻有針對ruby 1.8.4和1.8.5版本的,但是在ruby 18,6上面使用1.8.5的GC更新檔也完全沒有問題。GC更新檔的作用主要是針對Rails應用開大了ruby的記憶體堆,可以有效提高記憶體堆的使用率,降 低GC的頻率。根據Stefans Kaes提供的測試資料,打更新檔并且調整參數以後,GC的頻率下降到隻有原來的1/10還不到。降低GC頻率盡管并不能夠提高單個請求的執行速度,但是可 以增加整體應用的負載能力。

我們在JavaEye的伺服器上也使用了GC更新檔,并且根據推薦參數進行了調整。在使用GC更新檔之後,Web伺服器的CPU負載下降了大概15% 左右,效果非常顯著。當然開大記憶體堆的代價就是ruby程序會多消耗記憶體,在我們的伺服器上,ruby打更新檔之後多消耗了50%左右的實體記憶體。

二、緩存的使用

1、對象緩存

JavaEye上面關于對象緩存的讨論很多,我們也提供了JavaEye這方面很多資料,是以不展開了。CentOS RoR可以使用兩個對象緩存,一個是 CachedModel,類似Hibernate,比較簡單,對Model的CRUD操作自動進行緩存;另外一個是cache_fu,需要自己編碼來添加 對象緩存,但提供了更多進階機制,目前我們使用的是cache_fu。在使用對象緩存的情況下,應該把查詢方法的:include去掉,避免關聯查詢無法 利用緩存的現象。

2、查詢緩存

對于統計類耗時查詢,如果不要求實時性,那麼可以使用memcache-client将查詢結果緩存到memcached裡面,例如部落格排行榜之類。

3、頁面局部緩存

對象緩存和查詢緩存都是降低資料庫通路負載的,但如果CentOS RoR的負載很高,那麼隻能依靠頁面局部緩存了。傳統的網際網路web1.0網站很流行采用動 态頁面靜态化技術來提高網站的負載,但是對于web2.0網站來說,每個頁面都帶有登陸使用者的個人資訊,頁面的很多部分需要實時更新,例如投票,點選統 計,digg,顯示使用者線上狀态等等,動态頁面靜态化非常困難。當然如果你非要采用動态頁面靜态化,技術上也不是實作不了,可以通過AJAX請求來處理靜 态頁面的動态部分,但是這種解決方案的開發成本過高,而且性能未必會有明顯的改善,大家看看新浪和搜狐部落格就知道這種技術被應用的有多糟糕了。

web2.0網站比較常用使用頁面局部緩存,一種情況是頁面不需要實時更新的,那麼隻需要設定一個合理的過期時間就行了,這種情況我們目前使用的 比較多;另外一種情況是雖然不需要實時更新,但是會在使用者執行某些操作後需要緩存過期,比方說部落格個人首頁的很多頁面,這種情況下緩存過期政策會比較複 雜,考慮到合理的開發成本,我們尚未對這樣的頁面使用局部緩存。

此外,Rails的頁面局部緩存有一個缺點,就是和頁面查詢結果對應的Action當中的查詢語句要放在View裡面,否則每次action裡面的 查詢還是會被執行,但是這樣做會破壞程式代碼良好的MVC結構。這種情況下,也可以采用另外一個Cache插件: better rails caching,在緩存頁面的同時可以緩存Action當中的查詢語句。

三、應用代碼的優化

Stefans Kaes的文檔裡面對應用代碼的優化進行了非常詳細的介紹,是以我這裡隻提兩個比較重要的注意事項:

1、link_to

Rails的link_to是非常慢的,它的代碼實作過于複雜,特别是Rails1.2引入了REST以後,大量的命名路由被使用,這些命名路由 還需要通過一次method_missing,那就更加緩慢了。是以對于被頻繁使用的内部URL位址,一定要自己用字元串拼接方式改寫,可以很明顯提高 View的render性能。此外類似的helper還有很多,例如button_tag,p_w_picpath_tag啥啥的,如非必要,盡量不用他的 helper

2、正規表達式

ruby的正規表達式也是極慢,例如auto_fix這個helper的正規表達式就比較複雜,造成的結果就是一但大量使用 auto_fix,View的render就明顯變慢,類似依賴正規表達式進行字元串過濾的helper有很多,如果需要頻繁大量使用,請先自行做 benchmark。

Rails應用程式的記憶體洩漏問題和解決

記憶體洩漏是伺服器端程式經常遇到的,有時候記憶體洩漏問題會讓人很頭疼,總體來說,Rails的記憶體洩漏問題比Java要少得多,這是因為Java記憶體洩漏最常見的三種情況在Rails當中不存在:

1、HttpSession導緻的記憶體洩漏

Java程式員喜歡往session裡面丢很多東西,最糟糕的是竟然有很多架構軟體也肆無忌憚往session裡面丢狀态資料,但Rails的session是不放在記憶體裡面的,是以無此煩惱。

2、資料庫連接配接釋放不徹底

Java的資料庫連接配接池釋放不徹底,以及查詢遊标釋放不徹底,都必然導緻記憶體洩漏。Rails沒有資料庫連接配接池,而是每個程序持有一個長連接配接,是以不存在這個問題,而且由于持有長連接配接,也不存在Java裡面的OpenSessionInView的煩惱。

3、用靜态變量持有全局共享資料

Java程式員很喜歡通過靜态全局變量來持有共享資料,但共享資料忘記清理的話,也很容易導緻記憶體洩漏,Ruby是SNA架構,多程序伺服器模式,程序間無法共享資料,反而避免了全局共享資料帶來的麻煩。

但是Rails應用有一種情況:在Ruby代碼中調用C寫的第三方ruby類庫的時候,很容易導緻記憶體洩漏,但這種記憶體洩漏反而在Java中極其 罕見。Ruby本身有GC來管理記憶體堆,但是代碼一旦調用C寫的第三方ruby類庫,記憶體堆的配置設定權就掌握在第三方C庫的實作上面了,如果這個C庫的代碼 品質不夠好,記憶體洩漏就不可避免。由于ruby本身性能很差,是以計算量大的功能往往依賴底層的C庫來實作,這下記憶體洩漏的潘多拉魔盒就打開了!而 Java性能比較好,功能都是純Java編寫,基本上看不到需要依賴第三方C庫的情況,是以比較安全。

JavaEye也面臨着記憶體洩漏的困擾,這方面困擾主要來自于Rmagic。Rmagick調用ImageMagick的C庫來完成圖檔的操作, 從我們的監測來看,RMagick大多數情況下會緩慢的洩漏記憶體,在某些特定的圖檔操作上會急劇的洩漏記憶體。解決辦法就是用mini_magick替代 Rmagick,mini_magick是直接調用ImageMagick的mogrify指令,另起一個程序來操作圖檔,操作完程序就結束了,絕無後 患,由于Linux的fork程序開銷不大,是以也不必擔心性能問題。

此外,調用第三方C庫的ruby代碼編寫都需要高度小心,比方說JavaEye使用ferret實作全文檢索,根據應用的需要調用ferret的 API來編寫自己的analyzer,其中在實作token_stream方法上面使用了XXXAnalyzer.new和 XXXToken.new,XXXFilter.new,結果記憶體急劇洩漏,經過檢查發現是Analyzer對象不能被反複建立,改成建立後緩存該對象就 好了,但是Filter和Token對象卻必須每次建立,此外ferret的PerAnalyzerFilter也有記憶體洩漏問題。由于類庫是用C編寫 的,單純看API文檔或者看源代碼片斷一般無法判斷出裡面的記憶體洩漏陷阱的。

當遇到了難以解決和定位的記憶體洩漏問題,Ruby也有類似Java的記憶體Profiler工具:

1、Memory Profiler

一個純ruby編寫的記憶體探測器,原理很簡單,就是用ruby的對象引用計數器ObjectSpace.each_object去周遊記憶體堆中的 每個ruby對象,進行統計和分析。用起來很簡單,非常适合于開發環境下偵測記憶體洩漏問題,但不能用在生産環境下,極度影響Rails性能。

繼續閱讀