第五章 調優案例分析與實戰
5.1 案例分析
5.1.1 高效能硬體上的程式部署策略
一個15萬PV/天左右的線上文件型別網站最近更換了硬體系統,新的硬體為4個CPU、16GB實體記憶體,作業系統為64為CentOS5.4,Resin作為web伺服器。整個伺服器暫時沒有部署別的應用,所有硬體資源都可以提供給訪問量並不算太大的網站使用。管理員為了儘量利用硬體資源選用了64位的JDK1.5,並通過-Xmx和-Xms引數將Java堆固定在12GB。使用一段時間後發現使用效果並不理想,網站經常不定期出現長時間沒有響應的現象。
監控伺服器執行狀況後發現網站沒有響應是由GC停頓導致的,虛擬機器執行在Server模式,預設使用吞吐量優先收集器,回收12GB的堆,一次Full GC的停頓時間高達14秒。並且由於程式設計的關係,訪問文件時要把文件從磁碟提取到記憶體中,導致記憶體中出現很多由文件序列化產生的大物件,這些 大物件很多都進入了老年代,沒有在Minor GC中清理掉。這種情況下即使有12GB的堆,記憶體也會很快被耗盡,由此導致每個十幾分鍾出現十幾秒的停頓。
程式部署上的主要問題是過大的堆記憶體進行回收時帶來的長時間的停頓,在高效能硬體上部署程式,目前主要有兩種方式:
通過64位JDK來使用大記憶體;使用若干個32位虛擬機器建立邏輯叢集來利用硬體資源。
此案例中的管理員採用了第一種部署方式。對於使用者互動性強、對停頓時間敏感的系統,可以給java虛擬機器分配超大堆的前提是有把握把應用程式的Full GC頻率控制的足夠低,至少低到不會影響使用者使用,譬如十幾個小時乃至一天才出現一次Full GC,這樣可以通過在深夜執行定時任務的方式觸發Full GC甚至自動重啟應用伺服器來將記憶體可用空間保持在一個穩定的水平。
控制Full GC頻率的關鍵是看應用中絕大多數物件能否符合“朝生夕滅”的原則,即大多數物件的生存時間不應當太長,尤其是不能產生批量的、長生存時間的大物件,這樣才能保障老年代空間的穩定。此外,如果讀者計劃使用64位JDK管理大記憶體,還需考慮如下幾個問題:(記憶體回收導致的長時間停頓; 現階段,64位JDK的效能測試結果普遍低於32位JDK; 需要保證程式足夠穩定,因為這種應用要是產生堆溢位幾乎就無法產生堆轉儲快照(因為要產生十幾GB甚至更大的dump檔案),哪怕產生了快照也幾乎無法進行分析; 相同的程式在64位JDK中消耗的記憶體一般比32位JDK大,這是由指標膨脹及資料型別對齊補白等因素導致的。)
上面的問題聽起來有點嚇人,所以現階段不少管理員還是選擇第二種方式:使用若干個32位虛擬機器建立邏輯叢集來利用硬體資源。具體做法是在一臺物理機器上啟動多個應用伺服器程序,給每個伺服器程序分配不同的埠,然後在前端搭建一個負載均衡器,以反向代理的方式來分配訪問請求。讀者不需要太在意均衡器轉發所消耗的效能,即使使用64位JDK,許多應用也不止有一臺伺服器,因此在許多應用中前端的均衡器總是要存在的。
考慮到在一臺物理機器上建立邏輯叢集的目的僅僅是儘可能的利用硬體資源,並不需要關心狀態保留、熱轉移之類 高可用性需求,也不需要保證每個虛擬機器程序有絕對準確的均衡負載,因此使用無Session複製的親合式叢集是一個相當不錯的選擇。我們僅僅需要保障叢集具備親和性,也就是均衡器按一定的規則演算法將一個固定的使用者請求永遠分配到固定的一個叢集節點進行處理即可,這樣程式開發階段基本不用為叢集環境做什麼特別的考慮。
使用邏輯叢集會遇到如下問題:儘量避免節點競爭全域性的資源;很難最高效率的利用這些資源;各個節點仍然不可避免的受到32位記憶體限制;大量使用本地快取的應用,在邏輯叢集中會造成較大的記憶體浪費。
5.1.2 叢集間同步導致的記憶體溢位
一個基於B/S的MIS系統,硬體為兩臺2個CPU、8GB記憶體的HP小型機,伺服器是WebLogic9.2,每臺機器啟動了3個WebLogic例項,構成一個6個節點的親合式叢集,由於是親合式叢集,節點之間沒有進行Session同步,但是有一些需求要實現部分資料在各個節點間共享。開始這些資料存放在資料庫中,但由於讀寫頻繁競爭很激烈,對效能的影響較大,後面使用JBossCache構建了一個全域性快取。全域性快取啟用後,服務正常使用了一個較長的時間,但最近不定期的多次出現記憶體溢位問題。
在不出現記憶體溢位異常的時候,服務記憶體回收狀況一直正常,每次記憶體回收後都能恢復到一個穩定的可用空間,開始懷疑是程式的某些不正常的程式碼路徑中存在記憶體洩漏,但管理員反映最近程式並未更新或升級過,也沒有進行什麼特別的操作。只好讓服帶著-XX:+HeapDumpOnOutOfMemoryError引數運行了一段時間。在最近一次溢位之後,管理員發回了heapdump檔案,發現裡面存在著大量的org.jgroups.protocols.pbcast.NAKACK物件。
JBossCache是基於自家的JGroups進行叢集間的資料通訊,JGroups使用協議棧的方式來實現收發資料包的各種所需特性的自由組合,資料包接受和傳送時要經過每層協議棧的up()和down()方法,其中的NAKACK棧用於保障各個包的有效順序及重發。
5.1.3 堆外記憶體導致的溢位錯誤
5.1.4 外部命令導致系統緩慢
5.1.5 伺服器JVM程序崩潰