釋出或重啟線上服務時抖動問題解決方案

zhanlijun發表於2014-05-02

http://www.cnblogs.com/LBSer/p/3703967.html

 釋出或重啟線上服務時抖動問題解決方案

一、問題描述

      在釋出或重啟某線上某服務時(jetty8作為伺服器),常常發現有些機器的load會飆到非常高(高達70),並持續較長一段時間(5分鐘)後回落(圖1),與此同時響應時間曲線(圖2)也與load曲線一致。注:load飆高的初始時刻是應用服務埠開啟,流量打入時(load具體指什麼可參考http://www.cnblogs.com/amsun/p/3155246.html)。

 

圖1 釋出時候load飆高

 

圖2 釋出時候響應時間飆高

 

二、問題排查方法

     釋出時對資源使用情況進行監控。

1)通過top -H -p 查詢cpu使用率較高的執行緒,發現2129和2130這兩個執行緒cpu使用較高。

圖3 查詢cpu使用率較高的執行緒

 

2)通過jstack列印棧資訊,並將執行緒號2129和2130轉換成16進位制(printf "%x\n" 2129),分別為851和852,發現這兩個執行緒是編譯執行緒(表1)。此外當這兩個執行緒cpu使用率降低後load以及響應時間也馬上恢復了正常,時間點非常吻合。

 

表1 cpu使用率較高的兩個執行緒詳細資訊

"C2 CompilerThread1" daemon prio=10 tid=0x00007fce48125800 nid=0x852 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None
"C2 CompilerThread0" daemon prio=10 tid=0x00007fce48123000 nid=0x851 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None

三、現象解釋

      C2 CompilerThread執行緒專案啟動初期cpu使用率那麼高,它在幹什麼呢? 

      Java程式在啟動的時候所有程式碼的執行都處於解釋執行模式,只有在執行了一段時間後,根據程式碼方法執行的次數,或程式碼裡迴圈的執行次數等達到一定的閾值才會編譯成機器碼,編譯成機器碼後執行效率會得到大幅提升,而隨著執行時間進一步拉長,JVM的各種更高階的編譯優化手段就會逐漸加上,例如if條件的執行狀況,逃逸分析等。這裡的C2 CompilerThread執行緒乾的就是編譯優化的活。

     現在貌似可以解釋之前的現象了。

     在程式剛啟動的時候,java還處於解釋執行模式,因此服務效率很低,響應時間緩慢,處理得慢了,load自然也就高了。而當流量持續不斷匯入時,我們程式碼的很多方法執行次數不斷增多,此時C2 CompilerThread執行緒不斷收集優化資訊,並且開始將一些熱點程式碼優化編譯成本地機器碼,因此該執行緒的cpu使用率增高。而當C2 CompilerThread執行緒完成初始編譯優化過程後,C2 CompilerThread執行緒的cpu使用率開始下降,與此同時優化後服務的效能大幅提升,服務響應時間也大大縮短,load也下降。

     現在的癥結在於編譯優化過程持續時間較長,引起抖動如何降低編譯優化的持續時間呢?

四、解決思路

1)預熱

      如果在服務接受線上請求之前提前完成編譯優化過程,那麼將能避免此種抖動情況。一般的做法是預熱,有兩種方法:

      a)程式主動預熱:在啟動完成後,程式主動的訪問熱點的程式碼,確保主要的熱點程式碼已被編譯成機器碼後再放入流量,可通過-XX:+PrintCompilation來確認。

      b)複製流量預熱:通過tcpcopy軟體拷貝一份線上nginx的流量進行預熱,完成之後再匯入線上流量。

2)啟動多個執行緒進行編譯優化

     如果能加快編譯優化速度,那也能降低解釋執行階段導致的抖動時間。因此可以多拿幾個執行緒來做編譯,加快達到高峰效能的速度。

     可以使用-XX:CICompilerCount引數來設定編譯執行緒數目,這個值預設是2(之前在棧裡看到有兩個編譯執行緒),我們可以加到4。

3)採用多層編譯

      編譯方式有三種:1)Client模式;2)Server模式;3)Tiered模式。我們服務預設是Server模式。

      Server模式是採用c2高階編譯的,會比較耗時且要執行一段時間才會觸發編譯。 Server模式的優點是編譯後程式效率較高;

      Client模式比較輕量也比較快觸發(比Server模式觸發快),編譯優化後程式效率不如Server模式;

      Tiered模式是Client模式和Server模式的折中,一開始會啟用Client模式,可以在啟動後更快的讓部分程式碼先進入編譯優化階段,之後會啟動Server模式,達到程式效率最大優化的目的。

      Oracle JDK 7裡的HotSpot VM已經開始有比較好的Tiered編譯(tiered compilation)支援,可以設定引數-XX:+TieredCompilation來啟動Tiered模式,java 8預設就是Tiered模式。

      圖4是到http://www.javaworld.com/article/2078635/enterprise-middleware/jvm-performance-optimization--part-2--compilers.html擷取的不同編譯方式的效能比較圖,橫座標是時間,縱座標是效能。可以看出Tired模式開始階段效能與C1相當,當到達某一時刻後效能與C2相當。

      

圖4 不同編譯模式的效能比較

     

五、結果分析

       簡單起見採用方案2和方案3來進行優化。

       採用方案2和3之後進行了多次釋出,釋出時除個別機器load達到10之外,基本沒有過高現象(在2~4範圍內),並且短時間(2分鐘)內,load都會降到較合理水平(2左右),較釋出時的load來看,比優化前要好很多。

      方案2和方案3只是降低了抖動持續的時間以及抖動強度,並不能完全避免抖動。真正能避免抖動的方案應該是方案1,通過預熱的方式實現平滑釋出或重啟。

 

 

 

相關文章