本文發(fā)表在《程序員》雜志2008年第11期
PHP沉思錄之六:Drupal的性能問題
左輕侯
Drupal是一個基于PHP的開源CMS系統(tǒng),也是我認(rèn)為技術(shù)上實現(xiàn)得最好的一個PHP應(yīng)用。Drupal的架構(gòu)非常優(yōu)秀,通過微內(nèi)核+plugin的方式,實現(xiàn)了極佳的擴展性,從而使Drupal遠(yuǎn)遠(yuǎn)超出一般的CMS這一范疇。從這個意義上來說,把Drupal稱為Web OS似乎更加合適一些。關(guān)于Drupal,有太多的話可以說,也許我會在以后的時間里寫一篇文章對它進行專門的討論。但是在本文中,我想討論的,是Drupal社區(qū)中的每一個人都會面對,但不是每一個人都對其有清晰認(rèn)識的問題,即Drupal的性能問題。
因為客戶需求,我曾經(jīng)對Drupal做過比較全面的測試。當(dāng)時的環(huán)境是雙服務(wù)器(DB server+Web Server),硬件配置都是單CPU+4G。數(shù)據(jù)庫里面有幾千條Node記錄。用JMeter對各種情況下(開/關(guān)各種cache模塊,logged user/anonymous user)不同頁面的讀取和寫入操作都進行過測試。
測試的結(jié)果可能和很多人印象中不一樣。兩個主要的結(jié)果如下:
1. Logged user和anonymous user的性能差距非常大。同一個頁面,logged user的RPS(Requests per second)一般不超過20,而啟用了cache的anonymous user的RPS在100多,當(dāng)使用了file-based cache以后,甚至能超過300。
2. 數(shù)據(jù)庫壓力相對較小。由于Drupal把大量可配置的內(nèi)容都放在數(shù)據(jù)庫中,因此往往容易產(chǎn)生這樣一種印象,即Drupal對數(shù)據(jù)庫要求應(yīng)該是很高的。但事實上,無論是cache還是非cache模式,DB server的壓力都相當(dāng)小(CPU在10%以下),而Web Server的CPU在80%以上。跟蹤所有的db query的執(zhí)行時間后,也證明了這一點(全部db query的執(zhí)行時間只占頁面生成時間的一小部分)。
經(jīng)過反復(fù)的測試和思考,我得出了一些結(jié)論。很明顯,Drupal在大量logged user并發(fā)情況下的瓶頸,在于執(zhí)行Drupal代碼的CPU時間,而不是在于數(shù)據(jù)庫或者其他地方。之所以出現(xiàn)這樣的情況,和PHP本身的執(zhí)行機制和Drupal的實現(xiàn)方式有關(guān)。Drupal在生成一個非cached的頁面時,不管這個頁面多么簡單,都要執(zhí)行一個完整的bootstrap過程,即使只啟用了最少的模塊,這個過程也要調(diào)用幾十個PHP文件,執(zhí)行成千上萬行PHP代碼。而PHP的機制又決定了沒有任何PHP代碼或者對象能夠駐留內(nèi)存,每次響應(yīng)請求都必須執(zhí)行完整的初始化工作。而anonymous user之所以快,是因為Drupal在執(zhí)行cached page的時候,不會執(zhí)行完整的bootstrap過程,它先檢查是否cached page,是的話就讀取緩存,然后結(jié)束工作。這樣當(dāng)然就快了。
以這個結(jié)論為前提,可以解釋一些事情:
1. 為什么Drupal的性能在各種環(huán)境下相差并不多。無論是雙服務(wù)器,單服務(wù)器,甚至內(nèi)存非常小的虛擬機,logged user的RPS值往往總是在10~20之間。數(shù)據(jù)庫里面有幾百條,或者幾十萬條記錄,影響也不大。因為瓶頸并不在于DB或者內(nèi)存,而是在于執(zhí)行代碼的過程。
2. 為什么使用APC/XCache這樣的代碼優(yōu)化程序,能夠得到極大的性能提升。在我自己的虛擬機環(huán)境上,RPS從3~4提升到了12。因為它提升的是PHP代碼的執(zhí)行時間。
從這個結(jié)論出發(fā),列出一些對優(yōu)化Drupal的logged user性能有明顯作用和沒有明顯作用的措施:
沒有明顯作用的:
1. 加內(nèi)存。在并發(fā)數(shù)只有10+的時候,即使每個請求占20M內(nèi)存,也只有200M+內(nèi)存而已。
2. DB server和Web server分開,或者增強DB server的配置。一臺中等性能的mysql服務(wù)器,應(yīng)付200~300的并發(fā)是很輕松的事情,在并發(fā)數(shù)只有10+的時候,db server實際上是很空閑的。
3. 基礎(chǔ)軟件的優(yōu)化,例如從Windows轉(zhuǎn)移到Linux,從apache轉(zhuǎn)移到Lighttpd,從html' target='_blank'>MySQL遷移到其他數(shù)據(jù)庫,除了從Windows轉(zhuǎn)移到 Linux會有比較明顯的提升以外(因為PHP在Linux上的效率比在Windows上要好),其它的措施可能會快一些,但不會有大幅度的提高,因為瓶頸不在那里。
有明顯作用的:
1. 使用APC/XCache這樣的代碼優(yōu)化程序,速度會有幾倍的提升。估計大家都已經(jīng)這樣做過了。
2. 增加web server的CPU數(shù)量。雙核的肯定比單核的快,4個CPU肯定比2個CPU快得多。
3. 使用多web server+單db server的配置,把代碼執(zhí)行的壓力分散到不同的web server上。上文說到,單臺db server可以輕松應(yīng)付200+的并發(fā),這意味著理論上可以支持10臺以上的web server。
4. 使用Quercus這樣的引擎,把PHP代碼編譯成Java,再在Java VM中運行,理論上會有很大的提高。原因是,第一,Java的運行效率比PHP高,第二,Java代碼是可以cache的,不需要每次都重新加載。這里有個測試結(jié)果:http://www.workhabit.org/resin-backed-php-drives-4x-performance-improvements-drupal 。Drupal在Quercus下有4倍的性能提高,但是這個數(shù)字跟Drupal在打開APC/eAccelerator下的提升差不多,所以可能沒有太大的實用價值。
另外一種思路是代碼本身的優(yōu)化。
使用cache API基本上是沒有意義的,因為對于logger user,Drupal不會調(diào)用cache API。Drupal.org上有人提出,即使是logged user,有很多頁面也是不用定制化的,這意味著可以cache它們。但是Drupal沒有提供這樣一種機制。只要是logged user,Drupal就會執(zhí)行完整的bootstrap過程,即使只打印出一個hello world,因此實際上你沒有辦法在logged user狀態(tài)下cache單個頁面。
到目前最新版本的Drupal(Drupal 6.4)為止,對于logger user,Drupal只提供了一種cache功能,就是可以將部分block設(shè)置為可cache的。在block占用大量服務(wù)器時間的情況下,block cache能夠有效地提高效率。但是,由于block cache對于bootstrap過程并無影響,因此當(dāng)瓶頸在于bootstrap本身時,Block cache是無能為力的。
在Drupal.org的社區(qū),關(guān)于logger user的cache問題,一直處于熱烈的討論之中。基本的結(jié)論是,由于Drupal的架構(gòu)就是這樣,目前沒有很好的解決方案,只能期待Drupal在以后的版本中進行改進了。
我研究了一下Drupal的bootstrap過程,發(fā)現(xiàn)也許這樣是可行的:實現(xiàn)hook_boot函數(shù),這是bootstrap中執(zhí)行最靠前的一個函數(shù),它被調(diào)用時,bootstrap的大部分過程還沒有執(zhí)行。在hook_boot中,檢查當(dāng)前頁面是否需要cache,如果是,直接讀 cache生成頁面,然后調(diào)用exit()強行結(jié)束。這在理論上是可行的,但太過hack了一點。
Drupal的情況是這樣,那么其他的PHP框架,尤其是半官方的Zend Framework,性能如何呢?通過搜索,我在網(wǎng)上找到了一份PHP framework comparison benchmarks,網(wǎng)址見:http://www.avnetlabs.com/php/php-framework-comparison-benchmarks。根據(jù)這份報告的數(shù)據(jù),Zend Framework的性能只有原生PHP的10%,如果沒有用APC,連3%都不到。當(dāng)然,這份報告的數(shù)據(jù)不一定詳盡,Zend Framework在不同環(huán)境下的表現(xiàn)應(yīng)該也會有出入。但是,Zend Framework的性能大幅度落后于Baseline PHP,應(yīng)該是確鑿無疑的。
為什么PHP主流框架的性能都存在著這樣的問題呢?其實這也不難理解。回顧PHP沉思錄系列第一篇中對于PHP工作模型的討論,由于PHP沒有駐留內(nèi)存的進程,所以每一個request發(fā)生時,都必須初始化所有的對象,這導(dǎo)致大量的時間被耗費在進程代碼的執(zhí)行過程中。當(dāng)PHP程序僅僅是簡單的腳本時,這無關(guān)緊要,但是在結(jié)構(gòu)復(fù)雜的架構(gòu)中,由于每次處理request都要重復(fù)調(diào)用成千上萬行代碼,這一問題就變得非常突出了。而且,除非PHP以后的版本對這一機制進行改進,否則這個問題無法得到徹底的解決。
那么,這是否意味著,PHP只能適用于小型的網(wǎng)站,而無法在高并發(fā)量的大型網(wǎng)站施展拳腳呢?當(dāng)然不是這樣。事實上,在Yahoo和其他許多知名的巨型網(wǎng)站上,都大量地使用了PHP。原因在于,PHP僅僅被用作一個內(nèi)容生成器,生成的內(nèi)容會被轉(zhuǎn)化為靜態(tài)文本,絕大多數(shù)用戶瀏覽的都是被cache的靜態(tài)文本。這就和PHP程序的性能毫無關(guān)系了。但是,當(dāng)用戶并不是僅僅進行瀏覽,而是需要頻繁地和網(wǎng)站進行互動時,PHP的性能不但無法比擬C和Java,甚至無法與同為腳本語言的Python和Ruby相比。也就是說,PHP更適合于新聞門戶這樣的內(nèi)容發(fā)布站點,而不是web 2.0應(yīng)用的首選。
在本系列文章告一段落的時候,我們看到的是PHP的局限性。熱愛PHP的人們可能會對此覺得沮喪。但是,這并無損于PHP作為一門優(yōu)秀語言的聲譽。尺有所短,寸有所長,對于我們熟悉和喜愛的工具,我們更應(yīng)該了解它們的局限,這也有利于我們更有效地使用它們。
鄭重聲明:本文版權(quán)歸原作者所有,轉(zhuǎn)載文章僅為傳播更多信息之目的,如作者信息標(biāo)記有誤,請第一時間聯(lián)系我們修改或刪除,多謝。
新聞熱點
疑難解答
圖片精選