程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 網頁編程 >> PHP編程 >> 關於PHP編程 >> 一次php應用的優化實踐

一次php應用的優化實踐

編輯:關於PHP編程

之前做過的一次優化實踐,最近翻出來看看,有些通用的優化手段還是可以復用的。系統跑得時間長了,總會出現這樣那樣的問題和瓶頸,有了問題不可怕,我們有“打虎”的家伙事兒--無非就是定位問題->分析問題->提出解決方案->實踐->結果反饋->總結再優化。
問題描述:系統采用 PHP5 + Zend framework 開發,在數據規模和訪問量增加後(千萬級),出現了後台apache服務器負載過高的現象,在訪問高峰時段(比如每天下班到晚上10點這一段時間,特別是周五),機器CPU負載會飙升到170多,CPU負載過高造成處理請求也相應的變慢,所以亟需解決這個問題。
問題分析:通過連續幾天的觀察和分析,當CPU使用率達到100%時,其中系統CPU使用率占據了很大的比例,用戶CPU使用率倒不是很高,另外前端 haproxy 和 squid cache的cpu負載很低,memcached和squid的hit ratio一般都能達到60%左右。
分析backend的access-log,發現相當大一部分請求的User-Agent是搜索爬蟲;
同時,在 apache 上配置了xdebug,在空閒時段對主要的頁面的測量了一組性能數據,通過使用kcachegrind對測得的數據進行分析(如何配置xdebug,可以用soso搜一下),發現:
性能數據不夠穩定,同樣的請求之間測試數據會相差比較大
慢的點比較分散
memcached的訪問大部分的情況都比較慢(100ms以上)
解決方案通過上述初步的分析,對現有的程序逐步作了一系列調整。
首先考慮到的是是否可以想辦法增加前端squid cache的Hit ratio,從而減少穿透squid到達後端apache的請求數。
考慮到相當一部分請求來源於Crawler,而之前squid cache只會對設置了language cookie的請求作cache,而來自Crawler的請求都沒有cookie信息。於是想到把來自Crawler的請求都默認為language為zh_CN的,然後修改haproxy的配置,把User-Agent為常見的Crawler的請求都轉交給squid cache.
修改php代碼,把一些頁面的緩存時間設置得更長一些
經過如上兩個步驟,到達apache的請求確實減少了一些,但是這個對cpu負載過高的問題幫助很少,於是另尋它法。
其次,根據使用 xdebug profiling的結果來看,和memcached的交互耗時比較長,於是想是否可以想辦法讓memcached能更快地響應請求,從而使得每一次請求能更快完成,從而使並發降低。
通過代碼分析,發現線上memcached使用的是poll(),而memcached的連接數在繁忙的時候保持在1000左右,memcached的CPU使用率在 30% 左右。很顯然,poll()方式在處理如此多的並發連接時是很低效的。於是重新編譯memcached,使其使用epoll()的方式來處理請求,替換為epoll之後,memcached的cpu usage從 30%左右降低到 3% 左右,10倍之多!
另外,memcached的hit ratio不是特別高,而且被換出的item數也比較高,於是想到對cache的內容作partition.原本打算做 manually partition,後來發現php的最新的memcache擴展就能支持根據cache的key自動作partition,而且能在不修改程序代碼(需要修改配置文件:-))的情況下增加新的memcached實例。於是升級每一個apache的php memcache擴展,然後再配置文件中增加了一台新的memcached。到此完成memcached的內容partition。修改之後的效果比較顯著,頁面的載入時間比修改前縮短了很多。
經過這兩步的調整,memcached的效率比以前高了,但是apache的負載仍然居高不下,沒轍,再想其它辦法!
進一步深入分析前面說到主要系統CPU占用很高,要找原因只能深入內核了:) 從現在開始了我們的strace之旅。套用一句Nike的廣告詞:Just strace it!
在高峰時段對 httpd 進程進行了strace,方法不外乎如下這些
strace -p PID -c 得出 summary
strace -p PID -o output.log 寫入文件,慢慢研究
strace -p PID -e trace=file 只看 filesystem 操作相關的 syscalls
strace -p PID -elstat64,stat64,open,getcwd 只跟蹤這些 syscalls

從上述strace分析得到如下結論:
lstat64,stat64,open等 syscalls實在是多啊
上述 syscalls 占用時間確實不少! 60%以上的時間都被它們搶了, orz
絕大多數 syscall 是失敗的,真是屢敗屢戰啊
有了上述數據,我們就找到了問題的方向了,那就是找這些毫無意義的系統調用是怎麼來的。
經過分析,這些是php要加載某一個類時,會去 include_path 中定義的一系列目錄中搜尋該類對應的文件,挨個目錄這麼試過去,直到找到為止。嗯,這種方式顯然是比較低效的,有沒有更好的方式來完成這個事情呢?答案是肯定的,有!而且還有不止一種方法!
調用require_once()時,參數寫絕對路徑(開始Guys write Zend Framework就不懂這個道理;後來才有更新))
使用 __autoload()對class進行 lazy loading,也就是說真正需要的時候才去加載,而不是不管三七二十一把可能用到的類文件都require_once了。
問題是找到了,但是要解決這個問題還面臨著另一個問題。開發中代碼都注意用絕對路徑了,唯一可以改進的地方是改為 lazy loading,但是 Zend Framework中大量的require_once采用相對路徑,這個就是導致問題——這裡我說的問題是本文我們談論的CPU負載過高的問題——的根本原因。
OK,既然問題找到了,動手解決。寫個腳本自動生成 Class -> File Path 對應關系,生成代碼中所有類和Zend Framework中所有類的對應關系文件。把代碼中和Zend Framework庫中所有的 require_once 都注釋掉。然後進行詳細的測試,然後上線。結果令人吃驚,負載降到了 3 以內!!問題解決。
總結:
寫代碼的人都知道,可能出問題的地方總會出問題,任何問題都會有個原因(哪怕暫時沒有找到),從根上解決才是王道,解決什麼問題不重要,希望大家能學習這個解決的思路,善於利用工具。ok,這個case就這樣了。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved