本文主要是思惟性的總結,是總結優化的方法學,對方面上面的錯誤進行總結。不會涉及到前端具體的技術,好比對js和css進行壓縮、合併,減小http請求,緩存頭控制等等。這些那本《高性能建站指南》都有現成的。php
基於本人在多家公司分別遇到的網站速度與性能問題,多年所積累出的乾貨;有的開發10年經驗,在遇到網站速度問題時,也仍然在犯一樣的錯誤。css
1、背景與思惟方式
html
常見的狀況:前端
上面都只是猜想出的可能緣由。幾種狀況都有可能。上面的辦法都是能夠做爲優化的工做內容來作的。好比把js、css文件壓縮,確實是能夠提升速度,這是一個常識!java
還能夠把數據庫優化一下啊;能夠安裝什麼zend加速器,對代碼減小編譯環節。也能夠把數據庫的數據作一些磁盤級的緩存起來,減小操做數據庫。mysql
諸如此類的優化措施,都是可以提升速度的。nginx
但這些都存在思惟方式錯誤。在之前呆的好幾家公司,遇到速度問題,同事都抱着相似思路來優化的。結果都折騰時間和精力,仍是沒解決好。web
我發現,身邊一些同事的思惟方式是這樣子的:sql
我習慣不能靠猜、要以收集的數據爲依據(實時記錄下來的性能數據),好比作腳本統計每次執行耗時,收集必定量的數據,就知道最耗時間的地方在哪裏了,不是看錶象認爲哪裏慢就哪裏慢。如今,即使我大致判斷性能瓶頸在哪裏,我也不會說,我但願用數據來支持
數據庫
我把上面的叫作純技術思路,純技術思惟來看待問題。從技術角度,css和js文件壓縮、php程序優化、作緩存、數據庫優化,都是沒有錯誤的。從技術層面是往好的方面走。
就像解決12306的網上售票問題的時候,不少僅僅是技術思惟來解決。其實某人提出從業務層面調整售票方式,整個性能就會改善,理由是基於:人家現有的窗口售票可以知足平常所需,那自己不是這個系統存在瓶頸。
2、未必與工做年限長有關
從經從來看,我發現,網站速度優化,與多少年開發經驗沒有直接關聯,我之前分別遇到過有10年、20年軟件開發經驗的同事,在解決速度問題的時候,也是一種純技術思路。
這是幾年前的事了,我親身經歷的一個例子:技術經理是79年的。其實咱們的網站訪問速度還不錯。而老闆看到同行的網站網站怎麼快些。(人家纔多少個技術,咱們這麼多。應該要達到)。
技術經理把一些方法都嘗試了:研究同行的no-cache頭,唉,他們的怎麼有,而咱們的沒有。好,咱們也要像他們那樣作。咱們還把代碼片斷給緩存起來,花了很多時間。惋惜,結果速度沒有明顯提升。有,也只是根據感受:「哦,我剛纔加了緩存控制後,發現服務器的負載低了點。可是怎麼又變高了呢。」。「我已經開啓了壓縮,怎麼沒明顯改善呢?」,其實,加服務器緩存控制,代碼判斷緩存。確定是有效果的。但能提升多少?或者說目前這個不算瓶頸。就算你把其餘的優化得再好。也沒提升總體。沒找到瓶頸所在,結果各自方法都試過了。仍是沒用。白忙活。若是一開始可以分析出瓶頸。着力點徹底不一樣。
那晚技術部經理拉着技術部同事加班,仍是沒搞定。只能收工。瞎折騰。
另外,我記得當時在那個公司,後臺涉及不少統計方面功能,常常一點卡死,慢得已經不能正常使用。瓶頸就在數據庫。php程序方面沒多少能夠優化的,即使改寫程序,也不會發生本質改變,由於瓶頸不在這。
由於不是專業的運維人員,因此,咱們不知道怎麼去分析性能瓶頸。只是根據感受,這樣作能夠提升點性能。先這樣作吧。
後面還會說到我在c公司的例子,開發經驗20年。
其實,我有個習慣,不迷信權威,10年、20年工做經驗,只要你說的對,我都認同,但我不會不假思索被所謂10年、20年的經驗標籤所影響,結果接受錯誤的東西。好比是否使用存儲過程來封裝業務邏輯,在傳統銀行、金融領域作開發的人員傾向於使用(也許某些技術會被10年開發經驗變得不假思索),但在互聯網這種思路不見得是對的。
關於存儲過程封裝業務邏輯,請參考個人文章:http://www.cnblogs.com/wangtao_20/p/3475184.html
關於權威、頭銜對人思考的影響,請參考個人文章:http://www.cnblogs.com/wangtao_20/p/3652225.html
3、優化觀念的改善
一、主次矛盾思惟-定位性能瓶頸
在解決網站速度問題經歷中,我並非什麼都去優化。
個人辦法是,找哪一個是瓶頸,也就是主要矛盾。若是主要矛盾沒解決。對其餘小矛盾不管如何作優化,速度都不會發現明顯提升。這種思惟方式,也是某次遇到實際問題,同窗跟我說瓶頸在數據庫,所以我獲得啓發。在後續的優化工做中,我首先不會去作什麼優化,我會第一找到速度的瓶頸在哪裏,這種方式在實踐中與同事的進行對比,差別明顯:既解決了問題,又減小了本身的瞎折騰。
不少人聽我說到找瓶頸法,可能會以爲不覺得然,或者無動於衷。個人技術同事也是對我提出的方式,沒有明顯的感觸。這可能與我無法以口頭形式完整描述有關。
其實,這些都是我實踐出來的乾貨,其中有見過一些同事失敗的嘗試,最終才造成了一種規律性的東西。因爲經歷過的幾家公司遇到一樣的問題,後來發現不只僅是個例現象,是不少技術同事沒有學會正確的方法論,用試一試的心態,偶爾可能試對了,只是偶爾命中。之後遇到複雜點的,還靠試來試去,就沒眉目了。
二、瓶頸法,符合"先抗住再優化"的原則
好比偵測定位到數據庫是瓶頸所在,那麼優先解決數據庫問題,這樣可以保證抗住.而至於js、css壓縮節省帶寬等是屬於優化部分,後期再作,即使如今作了,速度並不會明顯改善.常常看到很多同事作着"多多益善"的無用功,因此我自信個人瓶頸定位法。與現實中的哲學也是很是符合的。如今再次感悟到,作技術的上升到哲學層面來解決問題,是必要的。這裏就是主次矛盾法。
這個觀點很受用。之前我寫過一篇文章,專門對此作了總結:http://www.cnblogs.com/wangtao_20/p/3243009.html
接觸的實際項目:電商網站(當時瓶頸在數據庫)、外貿網站(當時發現瓶頸在距離遠方面,程序優化作的是無用功,可經過帶寬來調整)、生活服務網站(發現瓶頸在程序方面)。定位過幾回速度問題的瓶頸,找到瓶頸,避免了無效的折騰。
概括爲:找網站目前性能的瓶頸在哪裏,把瓶頸解決了,其餘方面細微的影響速度,也是很小的.若是不去找瓶頸,把細微而非主要的矛盾去解決了,但因爲瓶頸沒解決,整個速度也不會快.
4、實際操做的幾大方面
依據http請求的原理,我通常習慣使用排除法,排除法依次爲:網絡帶寬緣由(包括距離遠)、程序執行速度緣由、數據庫緣由。這是依據一次http請求原理來定位的。
實際操做的幾大方面,我總結以下:
首先得了解一次http處理請求與響應的過程,不瞭解這種理論只能是瞎猜,猜b緣由,猜c緣由,而後用a試一試,無效(碰到運氣了恰好有效),再用b試一試。
根據http協議,任何一次http請求它的處理過程以下:
一、 瀏覽器向服務器發出請求。此時瀏覽器通常左下角會顯示:正在請求...
二、服務器接到請求後調用應用程序(php,java等,根據後綴名肯定)執行。服務器接到請求後會立刻返回一個頭信息給瀏覽器,此時通常瀏覽器左下角會顯示:已經鏈接到服務器(服務器接到請求)
ps:若是速度很快,瀏覽器左下角這些信息是一閃而過的。若是網絡出現延遲,這些信息會一直顯示在左下角位置。
三、php,java等代碼執行完畢
四、代碼執行完畢後,服務器會發一個http頭響應瀏覽器(也就是告訴瀏覽器你開始接收數據了,準備好吧)。
此階段瀏覽器在沒有接到這個頭信息以前,左下角會顯示:正在等待服務器響應...
五、瀏覽器接到服務器發給響應頭,開始接收了數據,瀏覽器處於接收數據狀態,
咱們會看到瀏覽器左下角會顯示:正在傳輸數據....。也就是這個狀態。
六、傳輸數據結束,完成一次http處理,整個過程http處理結束。
直覺上,根據瀏覽器左下角顯示的狀態信息,可以大致預估時間耗費在服務器端仍是帶寬層面。這須要瞭解http請求的過程方面知識。
不過,不是靠猜:感受這個是性能問題,也改善一點點。這樣子是多多益善,結果作白費工夫。要找到瓶頸,也就是主要矛盾,問題就容易解決。最好的定位瓶頸方法是用數據來進行偵測,有數據做爲支持可以準確的看出瓶頸。具體點就是,放偵測代碼,偵測每一個環境耗費的時間多少秒,就能知道瓶頸。
瀏覽器下方還一直顯示"等待響應...",說明服務器那邊一直沒有給予響應,瀏覽器一直處於等待服務器給予響應階段。不涉及到帶寬瓶頸(由於尚未到傳輸數據的階段,在此以前就已經耗費比較長的時間)。利用這個排除法,把性能定位到服務器端去。因而,進入下一階段,我要在服務器端寫程序來統計程序的執行速度(服務端是php、java、.net均可以本身寫代碼實現,什麼語言就用什麼語言來實現)
瀏覽器概括左下角的狀態依次爲:正在請求》》已鏈接到服務器》》等待響應》》服務器已響應》》正在傳輸數據
我想到幾個試驗來輔助說明瀏覽器左下角的狀態:
1、 用個php文件(若是你是其餘語言也能夠照此進行),代碼裏面循環10萬次。看瀏覽器怎麼反應
由於php一直在執行,須要比較長的時間,因此沒執行完,web服務器不會發響應頭給瀏覽器。咱們看到瀏覽器左下角的狀態是」等待響應」
二、還記得須要長時間執行的php程序嗎。我經常遇到數據庫大數據量的時候,一條sql查詢了好久,響應出現問題,因此php就一直在等待數據。那麼web服務器(apache,nginx)根本沒獲得php的處理結果(也就是說php腳本耗費時間太多,根本沒執行完)。此時在瀏覽器看到是」正在服務器等待響應"。
一、本身寫程序來統計程序執行耗時
服務器端,在php代碼層面作一個速度統計:在程序開始執行以前記錄一個時間點,結束的時候記錄一個時間點。結束時間減去開始時間($end_time-$begin_time),就是php腳本的執行總耗時,這個時間其中包括了查詢數據庫,等待數據庫響應的時間在內。
我在服務器端,對php程序的執行,作了執行耗費時長的統計,動態收集數據。也就不不能靠收集一兩次的執行來判斷。由於有的時候訪問比較多,有的時候服務器狀態比較好。網站運行是動態的,通俗的理解:你某個時候發現速度慢到須要5秒,等你去看的時候,速度又不慢了,1秒就打開了。由於速度還能夠了,你都沒法知道是哪裏的緣由。
因此,用數據來收集。收集100次,1000次總能瞭解哪幾個頁面速度慢、慢到耗時幾秒鐘。
方法比較簡單:跟什麼語言無關,無論是java、.net都差很少。在程序的開始和結束點記錄時間,結束時間減去開始時間,就是程序的執行時間。把執行時間寫到文本文件裏面去。
這個程序須要本身來寫的。
php的示範以下:
$begin_time = microtime_float();//執行開始記錄時間戳,精確到毫秒 /*中間要執行的代碼*/ end_time = microtime_float(); $exec_time = $end_time-$begin_time;//執行時間計算 @save_stat($exec_time);//把執行時間寫入到文本文件中 /*下面是定義的兩個函數*/ function save_stat($time) { static $call_count=1;//統計調用多少次 $call_limit = 10;//遇到併發問題的時候限制嘗試多少次 if(!$time) return ; $exec_stat_file = "./exec_stat_file.txt"; $fp = fopen($exec_stat_file,'ab'); if(flock($fp,LOCK_EX)) { $s = 'access:'.date("Y-m-d H:i:s").',execute time:'.$time.'s,request_url:http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']."||".PHP_EOL; fwrite($fp,$s); flock($fp,LOCK_UN); }else{ usleep(1000); if($call_count<$call_limit) { $call_count++; save_stat($time); } } @fcolse($fp); //var_dump($fp); } function microtime_float() { list($usec, $sec) = explode(" ", microtime()); return ((float)$usec + (float)$sec); }
下面是我上面統計程序收集的數據示範:
經過這種方式。基本上可以判斷出,是否是php執行速度慢了。好比在瀏覽器打開一個網頁,我基本上都須要等上5-6秒鐘才能徹底看到頁面,我要判斷是否是程序性能緣由的話,我經過這個數據看出來。程序耗費時間都在2-5秒了,那麼web服務器就須要等待這個時間後,才能給瀏覽器發響應頭信息。這涉及到http請求原理,熟悉點這個方面頗有必要,我根據瀏覽器左下角顯示的文字信息來大致判斷是否是前端緣由,就是根據http請求過程來的。
二、定位哪一個代碼段耗費時長
在php程序方面。究竟是哪一個地方的代碼耗費了比較長的時間了,是寫得很差嗎?不知道某個地方的代碼,有專業的工具來定位一段php代碼哪些地方各耗時長的。我不清楚java、.net方面的工具,應該都有。php方面的工具我所知道的有兩個:一個是開源的xdebug,這個是一個調試php代碼的工具。兼帶有偵測代碼性能統計的功能。另一個是facebook公司的的xhprof,這個是他們公司本身開發的,由於facebook是用php開發的。xhprof如何使用,參考我原創的文章:http://www.cnblogs.com/wangtao_20/p/3320497.html。
這類工具的好處:精確到哪一個函數執行用時多長時間,最耗費時間的代碼在某處都能反應出來等等。cpu和內存耗費狀況都能記錄到的。我用這類工具可以判斷程序的執行速度是否爲瓶頸所在。
此方法還能夠結合http原理來估算:看瀏覽器左下角的狀態顯示"正在等待響應..."耗時長,而且結合我放的統計執行時間長短的數據。
若是一直瀏覽器左下角卡在"正在傳輸數據....",那就不是服務器緣由了,由於服務器此時已經發送響應給瀏覽器,瀏覽器處於接收數據狀態。兩點可能:傳輸距離遠,或者帶寬小。
這裏有個知識點:好比php程序(其實java、.net也同樣)從開始到結束耗費5秒中。實際上須要查詢數據庫獲取數據,若是數據庫壓力比較大,程序其實一直在等待數據庫給予數據,只有獲取數據完畢後,纔會接着執行後續代碼。
因爲上面腳本統計了程序執行耗費了多少時間,這個時間包括了從數據庫獲取數據的時間在內。
要想定位是數據庫層面的,能夠經過sql慢日誌來記錄,記錄超過多少秒的sql記錄到日誌中去。這個在mysql中能夠配置。參考:http://www.cnblogs.com/wangtao_20/p/3304645.html
我在實踐中發現,服務器級別記錄的只能做爲參考,由於mysql服務器級別只能記錄單條超過多少秒的sql。好比:一次頁面會執行5條sql,每條sql都沒有超過1秒啊,因此沒有一條被記錄到慢日誌中去。或者一個頁面變成是10條sql了
一條 0.1s,10*0.1 就是1秒了。
因此,我想知道a頁面執行完畢,數據庫層面查詢數據到底耗費了多少時間(本質就是sql執行時間),怎麼辦?我要須要在程序方面作寫代碼進行統計。就是把每一個頁面執行的sql都記錄下來,累加,就是a頁面數據庫層面耗費的時間。
我記得在a公司,遇到常常後臺統計的功能,一點擊就慢的問題,我當時沒什麼經驗,使勁去優化php程序。想着是否是循環太多了,若是每次循環都須要去查詢一次數據庫獲取數據。那麼多少次循環,就須要多少次。這種方式是否是很差,改爲一次查詢獲取全部數據。而後在程序端用php來聚合?
其實當時沒有造成如今方法論。因此本身折騰了不少精力,畢竟我當時對後端優化沒什麼經歷。畢業時間短。後來同窗啓發了我,不要去優化php程序了,大家的瓶頸在數據庫層面。時間耗費在了php一直等待數據庫給予數據。
教訓:當時的方向應該是往數據庫層面去優化。其實我感受,只要定位瓶頸在數據庫層面了,解決辦法就進入一個新的領域了:數據庫優化層面。數據庫優化方法比較常規好比表結構調整(分表、大字段拆分)、索引等等方面去。
例子
1、 靠猜想、主觀感受。我在C公司的例子
我在c公司遇到兩次速度慢的問題。
公司的生活服務網站速度慢了,集中在幾個頁面慢,不是全部頁面都慢。老闆反映這個問題,但願技術解決。網站用戶和訪問量不多。
此次,由我來解決這個問題。個人思路是要找到速度的瓶頸在哪裏。我先不關注要不要作js和css壓縮、數據庫優化等。
一樣仍是在這個公司,第二次遇到網站速度慢的問題。老闆開會反映給咱們。一個運營A,他曾經作過程序開發,立刻第一反應說:是帶寬緣由,由於咱們網站購買的帶寬只有1m。
散會後,其餘部門在使用中也再次提到速度慢的問題,這位同事A,也在辦公室,跟你們說是帶寬小的緣由。
我儘管知道,咱們網站購買的帶寬1m,不算大。但因爲我沒作數據統計,尚未定位到速度慢的瓶頸在哪裏,是帶寬,仍是程序速度、仍是數據庫問題?基於我之前的經驗,我都不敢隨便去猜想,此時我通常不喜歡直接代表態度是哪裏的緣由。我要用數據在支持個人判斷。
ps:根據我過去的經驗和直接來判斷,說是帶寬未免太早,由於在此以前呆的兩家公司我早就遇到過速度問題,是本身在錯誤中提高出來的方法。若是瓶頸不在帶寬呢?就算把帶寬加到4m都不會有改善。我其實也不喜歡這位同事嗷嗷叫,因此內心下決心,我會去私下裏偵測一些網站速度的方面的數據來幫助我進行判斷緣由。那個時候就能夠打他嘴巴的感受。
不過,這次不是我來主導解決速度問題。由於會議期間,技術部有位同事B,他主動跟你們說由他來解決網站速度慢的問題。
B同事擁有將近20年的軟件領域從業年限,他對於編碼和設計模式方面有很豐富的經驗。不過我發現他去解決網站速度的時候,他也沒有抓瓶頸,他以爲html頁面都不夠整潔,往html結構調整和js、css壓縮方面去下功夫。
雖然不是我負責弄速度這個工做,不過我以爲,這位同事也是抱着」看哪些須要優化,就作什麼」的思路去改善的,好比html結構確實須要優化,去作這方面優化確定使得網站只會往好的方面發展,不會往更差方面走。
我根據過去的所在公司的教訓,我去觀察了一下咱們網站慢的的幾個頁面。仍是會用到找瓶頸法,而後遵循的是「網絡速度(http請求耗時)>程序速度>數據庫速度」,實際上是」由前端到後端的」逐個排除的思路。
首先、打開網頁的時候,確實要五、6秒才徹底打開一個頁面。是否是網絡帶寬緣由?
若是是帶寬緣由的話,是帶寬不夠,或者距離遠嗎?我注意觀察了瀏覽器左下角,並不會立刻看到」服務器已響應」的文字,左下角狀態欄中,耗費時長的狀態並非在」正在傳輸數據….」。發現,耗費時長在於」等待響應…」
其實根據http請求和處理過程,就大致判斷,」等待響應..」,服務器其實尚未給予響應。這個只是大致判斷,我估算不是帶寬緣由。我不但願靠主管判斷,但願用進一步的數據收集來作出定位。
由於網站不是一兩個頁面,這個同事B他費了2天時間作這方面優化、因爲中途有其餘事情。這方面也半停半工的狀態。我以爲他的方向錯誤了。
我在服務器端,對php程序的執行,作了執行耗費時長的統計。
下面是我收集的數據。
還有一部分相似的數據我沒有貼上。有的耗費時間5秒左右的。
能夠看到,php程序執行時長都耗費了好幾秒。瓶頸在程序的執行速度上。
經過這一步,能夠推翻之前的判斷了。
一、瓶頸根本不在帶寬1m上,能夠排除帶寬的緣由了。推翻了以前那個同事主觀判斷帶寬緣由。看來靠猜想,沒有數據作支撐來優化是不行的。我看把帶寬加到10m,這邊程序執行速度也須要好幾秒。能改善嗎?瓶頸不在帶寬。無用功!
二、也不在js和css文件的壓縮、html結構優化上,這些不是主要矛盾。優化是能夠,但後期去作。
由於,這個程序執行時長包括了操做數據庫獲取數據的時間(由於要等待嘛)。
那究竟是不是數據庫的瓶頸呢?結合數據庫壓力是否是夠大,數據庫的數據量多大。
其實
其實使用xhprof這個工具,可以知道那段代碼耗費時間比較長了。想弄個圖,惋惜圖丟了。
記得當時結果是:那段php代碼重複調用文件緩存(把數據庫數據緩存在文件中了)不少次,xhprof這個工具記錄下了這個耗時點。兩個頁面的,xhprof工具當時統計出耗時5秒多和耗時3秒多的。
2、 b公司,帶寬能夠改善來提高
由於服務器在國外,距離比較遠。公司網站自己不給國內人使用的,根本無法測驗(國內訪問國外,這麼遠距離確定慢,很正常,不予理會)。但他們反映的是國外客戶訪問那個網站也慢。
記得b公司的同事把重點放在各類加速插件上面去,無改善。但是我用腳本統計程序執行時長,發現耗費時間基本上在1s之內,由於這種程序執行速度統計都已經包括了數據庫查詢的時間在內的了。基於http請求原理和程序執行速度的數據收集,我判斷,在數據庫優化、php程序性能方面都沒有多大的優化空間。根本不是瓶頸所在。
個人方向集中在了:遠距離和帶寬層面的改善上面。而不是服務器自己性能和程序優化上。好比,由於有n個網站,此網站客戶若是是針對英國的,那麼租用的服務器最好安放到英國,不要跨到其餘國家了。緣由是:
一、 跨國的距離太遠。遠距離傳輸損耗快不到哪裏去。
二、 國與國之間的網絡確定要比本國內各個省份之間的網絡繁忙得多。畢竟此通道是一國全部向外數據的通道。好比中國與美國網絡通訊,是經過東部海底光纜聯繫的。意味着整個中國通向美國的數據訪問都是通過同一道線路的。
儘管網站的訪問量和數據量並不大,但瓶頸仍然定位準確了,從帶寬和距離層面入手去解決可以突破瓶頸。
本文完