這篇文章是關于網站性能優化體驗的,性能優化是一個復雜的話題,牽涉的東西非常多,我只是按照我的理解列出了性能優化整個過程中需要考慮的種種因素。點到為止,包含的內容以淺顯的介紹為主,如果你有見解能告知我那再好不過了。無論如何,希望閱讀它的你有所收獲。
?
我眼中的網站性能問題都反映了一個網站的“Availability”(中文叫做可用性,但是這個翻譯也不足夠達意),以往我的認識是,這個網站如果全部或者部分不可用,那是功能問題,但是如果響應慢、負載差,這才是性能問題;可是后來我逐漸意識到,性能問題涵蓋的范圍更廣,我還沒法給出一個準確定義,但是許多非業務邏輯錯誤引起的網站問題都可能可以算做性能問題,比如可擴展性差,比如單點故障問題。
?
?
在網站性能優化的最初階段,也就是所謂的“第一重境界”,做局部的定位、分析和修正,考慮的僅僅是“優化”,這也是初涉性能優化問題的大多數人的認識。在問題發生以后,發現它和業務邏輯沒有太大關系,就開始嘗試尋找問題產生的原因并加以解決。
?
無論是網站無響應還是響應緩慢,還是響應曲線異常波動,比如,可以圍繞CPU的使用問自己這樣幾個問題:
?
- 從CPU使用看系統是否繁忙?
- 如果系統繁忙,系統在做什么,為什么?(典型問題:HashMap不安全并發導致的死循環)
- 如果系統空閑,那么瓶頸在哪里?(典型問題:IO無響應)
- 如果響應波動,是否存在周期,周期是什么?(典型問題:連接迅速占滿,每一周期批量超時斷開一批)
- 如果響應波動,性能到波谷時系統在做什么?
- 是否有背景CPU使用?(即無壓力下觀察CPU的使用情況。典型問題:正執行的定時任務占用過多系統資源)
在這些問題中,情況雖然千變萬化,簡單地說,CPU的使用是核心,CPU使用率高,說明可能系統在實實在在地做事,反之,需要尋找其他瓶頸。通過結合進程、線程的快照,來初步確定問題的范圍。CPU使用率低的情況居多而且容易定位,只需要尋找其他的系統瓶頸;CPU占用率偏高的問題往往比較不容易定位,雖然也有一些辦法。關于具體性能問題的定位技術,這里不著過多筆墨,后續有機會詳細介紹。
?
對于一個剛開始做性能優化的網站系統,下面的事情不妨都做一做,會有立竿見影的效果(如果你需要更多的建議,不妨參考這張圖):
?
- 對于使用的成熟的技術,技術社區、官方文檔,往往會給出這種技術的白皮書或者優化指導,請參考。比如?Struts2的官方性能調優指南、Java6性能優化白皮書。
- 平臺和虛擬機調優。對于使用平臺和虛擬機的項目來說,這是必須要做的,一個JVM的參數可以對系統有顯著的影響。比如Linux下連接管理的參數,JVM關于堆大小分布的參數等等。
- 前端審查。這里的審查指的是通過Page speed、YSlow等工具,以及一些業界通用的法則和經驗(比如yahoo的若干條前端性能優化法則)來評估現有頁面的問題。
從使用的工具上說,性能問題的定位很大程度上是面向操作系統、虛擬機系統的問題定位(這里有一些定位方法介紹)。從問題定位的時機上說,又可以分為:
?
- 截取型:截取系統某個層面的一個快照加以分析。比如一些堆棧切面和分析的工具,jstack、jmap、kill -3、MAT、Heap Analyser等。
- 監控型:監視系統變化,甚至數據流向。比如JProfiler、JConsole、JStat、BTrace等等。
- 驗尸型:系統已經宕機了,但是留下了一些“罪證”,在事后來分析它們。最有名的就是JVM掛掉之后可能會留下的hs_err_pid.log,或者是生成的crash dump文件。
?
?
好,暫時說到這里,下面來看第二重境界。達到這重境界意味著已經能夠跳出“事后優化”的局限了,在設計和編碼的過程當中,能夠正式和全面地考慮性能的因素,比如:
?
- 減少使用時間敏感的容器管理,而使用容量或數量敏感的容器管理。比如我往一個緩沖里面存放若干數據,一種設計是每10分鐘flush入庫一次,還有一種設計是數據到達10M大小的時候flush入庫一次,通常情況下,你覺得哪個方案更可靠?
- 線程的統一管理使用。我的經驗是,10次對線程創建或者線程池的使用,往往就有5次是會出問題的。
- 避免使用同步Ajax。同步Ajax會造成瀏覽器假死,直至響應返回。
- 分析對同步、鎖的使用。即便在一些有名的開源庫中,我們也不止一次發現過不合理的同步設計,N多數據,單一的全局同步塊(這是一種性能設計層面上的“中心化”),結果它就成為了瓶頸,改動還不容易下手,很麻煩。
對于不成熟的團隊,建議能安排有經驗的程序員把關設計文檔和編碼中的性能問題,把常見的問題列出來參考學習。
?
達到第二重境界還有一個明顯的特征就是在軟件流程的前中期就開始做性能目標的論證和性能問題的驗證:
?
- 性能切面分析。這指的是在系統設計初期,為了評估一個系統的性能表現,做出一個性能類似的系統原型,并對其做性能測試和評估,這時候因為性能問題而涉及到方案的變更,影響較小。據我所知,能夠做到這一點的項目極少。在大多數團隊中,依賴于架構師和掌握話語權的設計者依靠經驗來避免性能問題帶來的大的方案變更(或者,干脆摔一次跤,再進行痛苦的“重構”)。
- 性能的自動化測試驗證。這一步必須伴隨著Coding進行才有較大的意義,以便盡早發現性能問題。
- 設計和代碼層面的評審。我的博客里面一再地強調評審的價值,不妨看看這篇和這篇。其實功能問題考慮得多、暴露得早,真正有危險的往往都是那些被忽視的非功能性問題,比如性能問題。
?
最后是第三重境界。達到這重境界的團隊能夠在早期規劃構想階段就將性能作為一個必備因素包含在內,這可不是隨口說說的經驗的估計,而是要有數據驅動的理論設計,比如做性能建模,根據市場大小、業務量、服務等級等等計算出性能的具體指標,并且在此要求下做合理的架構設計。
?
這里涉及的東西有很多,除了數據,還需要有大量的思考,對于一個網站來說,不妨問問如下的問題:
?
- 數據量會有多大,我該設計什么樣的存儲?一致性的要求又如何?
- 實時性要求是怎么樣的?用戶可以接受多少時間的數據延遲?
- 網站需要考慮到什么程度的可伸縮性?
- 哪些流程的數據處理有性能風險,數據量是什么級別的?怎么解決這個問題?
- 主要的業務時間消耗是怎樣的,我需要設計怎樣的業務流來滿足?
所有的性能問題和其他一切非功能性問題一樣,都是一定程度上的trade off,所以越優秀的設計者越需要思考,來規劃這些問題的解決方案。在規劃中因為性能問題而涉及到的因素有哪些,太多太多了,這里列了一些供參考。
?
要達到第三重境界還要能夠預測性能問題。這就需要成熟的監控體系,監控系統的變化,盡快做出反應。
?
比如國內發生了重大事件,用戶量陡增,監控系統能夠及時識別出用戶量監控曲線一個非常明顯的跳躍過程(比如持續事件超過某個值,且曲線斜率超過某個值),發出告警,并且自動擴容來應付潛在的風險。這些,都是建立在常規的業務運營數據收集基礎之上的,然后需要做數據挖掘,給出關鍵點。
?
再比如互聯網應用“緩存為王”。對于緩存的設計,甚至很大程度上決定了應用的成敗(如果你很有錢,靠大量的CDN這種非常規路線的另說,呵呵)。緩存的設計需要考慮到緩存的大小、分級、隊列、命中率計算、生命周期、更新換頁、數據分發、數據一致性和數據持久化等等問題,這些東西往往被很多只重視那些頁面展示效果和功能的人所忽視,但如果你是優秀的設計者,你需要積累這些思考。
?
Think big。有這樣一個真實的例子,我們曾經發現頁面模板的OGNL性能不高(兩次反射之故),遂在項目中把大部分OGNL表達式都改成了EL表達式,花了很多時間精力,性能也確實提高了,但是能提高多少呢?大概只有30%,這是一種細水長流的改進,對系統的破壞性不大,但是收效也不足以令人沾沾自喜,還失去了一些OGNL的靈活性。之后,我們換了一個思路,從大局入手,給頁面劃分區域,定制緩存框架,引入頁面緩存能力,雖然整套方案有些復雜,但是這種架構上的進化,由于頁面的生成或者部分生成直接命中了緩存文件,性能一下有了飛躍,提高了600%~800%。這就是Think big,從大處著想,見得到工程大塊的結構,需要足夠的視野、足夠的經驗和積累,可以帶來顯著的效果。
?
通常系統容量的設計都會要求到峰值容量以上,如果是像秒殺、搶購之類對性能要求非常高的系統,往往還存在一個問題:設計了這么大的容量,平時大部分時間業務量都比較小,這些資源浪費怎么辦?(題外話:這大概也是Amazon涉足云存儲和云計算的初始緣由吧)
?
同時,也要看到,性能因素也是一個網站系統發展的最大推動力,再細致的思考也難以兼容那么多未知的場景,不妨多在擴展性和兼容性上下下功夫,避免網站冷清痛苦,網站大熱更痛苦。