Java GC專家系列4:Apache的MaxClients設定及其對Tomcat Full GC的影響
本文是GC專家系列中的第四篇。在第一篇理解Java垃圾回收中我們學習了幾種不同的GC演算法的處理過程,GC的工作方式,新生代與老年代的區別。所以,你應該已經瞭解了JDK 7中的5種GC型別,以及每種GC對效能的影響。
在第二篇Java垃圾回收的監控中介紹了在真實場景中JVM是如何執行GC,如何監控GC資料以及有哪些工具可用來方便進行GC監控。
在第三篇GC 調優中基於真實案例介紹了可用於GC調優的最佳選項。同時也描述瞭如何通過降低移動到老年代中物件的數量來縮短Full GC耗時,以及如何設定GC型別及記憶體大小。
本文將介紹Apache的MaxClients
引數的重要性以及在GC發生時對系統整體效能的顯著影響。通過幾個例子,你將會更清晰的理解MaxClients
值所引發的問題。最後會介紹如何依據系統的可用記憶體來為MaxClients
設定合理的數值。
MaxClients對系統的影響
NHN的服務執行環境中有大量的流控(Throttle valve)選項,這些選項對系統的穩定執行具有重要作用。我們來看下Apache的MaxClients
選項在Tomcat發生Full GC時會對系統帶來哪些影響。
大部分的開發人員都知道GC 發生中會伴隨著”stop the world(STW)現象”(具體詳情參考理解Java垃圾回收)。尤其是NHN的Java開發人員可能都經歷過在Tomcat中由GC相關問題而導致的系統崩潰。因為JVM管理記憶體,因此Java應用系統不可避免的會遇到GC引起的STW現象。
在你開發的線上系統中,GC每天都會發生很多次。在GC發生時,即便TTS沒有發生,卻依然可能會給使用者503的錯誤響應。
系統執行環境
根據結構特點,Web服務更適合於做橫向擴充套件而非單純的提高單一機器的效能。所以通常根據效能需要,Web服務的伺服器部署結構由一臺Apache伺服器和多臺Tomcat伺服器組成。在本文中,假設一個Apache服務和Tomcat服務部署在同一臺物理主機上,如下圖所示:
圖1: 本文假設的服務執行環境
作為參考,本文所述引數均是基於Apache 2.2.21(prefork MPM),Tomcat 6.0.35,jdk 1.6.0_24,並執行在CentOS 4.7.2(32位)作業系統上。
系統記憶體2GB,並使用ParallelOldGC垃圾回收,預設開啟了AdaptiveSizePolicy
選項並設定堆大小為600MB。
STW 和 HTTP 503
假設Apache的流量為200QPS,並開啟10個httpd處理程式(儘管實際場景依賴於請求的響應時間)。在這種前提下,假設full GC導致的停頓耗時1秒,如果Tomcat發生了Full GC將會怎麼樣?
首先你能想到的是full GC導致Tomcat停頓,處理中的請求將得不到響應。如果這樣,Tomcat暫停,請求得不到處理,Apache將會怎麼樣?
即使Tomcat因Full GC而暫停處理,而請求卻仍以200 req/s的速度到達Apache。在full GC發生前,只需要10個或者稍微多一點的httpd程式就可以快速響應服務請求。但是現在Tomcat暫停了,為了處理新的請求Apache將持續建立新的httpd程式直到httpd.conf檔案中定義的MaxClients
閥值。因為MaxClients
預設值為256,所以200 req/s的請求並不會帶來太大問題。
這個時候,新建立的httpd 程式會怎麼樣?
Httpd 程式使用mod_jk模組管理的AJP連線池中的空閒連線把請求傳送到Tomcat。如果沒有空閒連線,則會要求建立新的連線。然而因為Tomcat處理暫停狀態,新建連線的請求將被拒絕。所以這些請求將會放到堆積佇列(backlog queue),佇列的長度是server.xml的AJP Connector中設定的。
如果請求資料超出了堆積佇列的長度,Apache將會收到連線拒絕錯誤,並把這個錯誤以HTTP 503的方式返回給使用者。
在本例的中,堆積佇列的長度預設設定為100,而請求速度為200 req/s,因此在由full GC導致Tomcat暫停的這1秒中,將有超過100的請求將會收到503錯誤。
Full GC結束之後,堆積佇列中的socket連線會被Tomcat接收並分配給工作執行緒(最大工作執行緒數由MaxThreads
決定,預設值為200)來處理請求。
MaxClients和堆積佇列
在上面的場景中,如何設定才能避免給使用者返回503錯誤?
首先我們需要知道,應該設定足夠的堆積佇列長度以容納在Tomcat Full GC導致的暫停期間流入的請求。因此堆積佇列最小長度至少為200(上文中QPS為200)。
這樣配置以後,是否還有其他問題?
把堆積佇列長度設定為200後,我們再次重複上面的場景。結果問題卻比之前更加嚴重。
正常情況下系統記憶體使用量維持在50%,而在發生Full GC時記憶體使用卻迅速上升到100%,引起記憶體交換區(swap)使用量的極劇增加。更為嚴重的是Full GC導致的響應停頓由原來的1秒增加到了4秒,直接後果就是期間系統像掛掉了一樣,不能響應任何請求。
在之前的場景中,只有100左右的請求會收到 503 的錯誤,而增加堆積佇列到200後卻導致了500甚至更多的請求被掛起至少3秒不能收到任何響應。
這個例子很好的證明了如果不能準備的理清配置資訊之間的因果關係,可能會對系統帶來極為嚴重的影響。
為什麼會這樣?
原理就是要清楚MaxClients
選項的特性。
MaxClients
的值不易設定過大,設定MaxClients
的關鍵在於即便建立了MaxClients
數量的httpd程式,也要需要維持應用系統的記憶體使用量不應超過80%。
系統交換區預設值為60,因此如果記憶體使用超過80%,系統將會發生頻繁的記憶體交換。
我們再來看下為什麼這個特性會導致上面所述的嚴重後果。
當請求的QPS為200時,Tomcat會被Full GC暫停響應,然後把堆積列隊容量設定為200。起初大約有100個額外的httpd 程式會被Apache建立,緊接著記憶體使用量超過了80%,引起作業系統主動的使用交換區的記憶體空間,而因GC存活在JVM老年代中的物件被作業系統誤認為長時間未使用,從而導致這些物件被移動到交換區。
最後,當GC過程中涉及到交換區時,耗時就會迅速增加。而後httpd程式數繼續增加,導致記憶體使用量達到了100%,從而出現了上述的嚴重後果。
上述案例的前後區別僅在於堆積佇列的長度:100和200。但為什麼在200時會出現更嚴重的狀況?
原因是堆積佇列不同的長度導致了httpd程式數的不同。當值為100時,在發生Full GC時100個請求所要求建立的連線被置於堆積佇列中。再有新的請求會被拒絕並返回503錯誤,所以系統的整個httpd的程式數僅超出100很少的數量。
但當佇列長度設定為200時,有200個請求被接收並置於佇列中。從而導致httpd程式的數量超過200,並觸發了作業系統進行記憶體交換的閥值。
所以,如果不顧記憶體使用情況而一味的加大MaxClients
的數值,將會導致Full GC時httpd程式數迅速增加,引進記憶體交換並最終降低系統的整體效能。
所以如何設定MaxClients,如何找到當前系統的閥值?
MaxClients 取值的計算方式
如果系統總記憶體為2GB,設定MaxClient
的值需要保證在任何時候記憶體的使用量不超過80%即1.6GB,從而避免因記憶體交換導致的效能下降。也就是說僅有1.6GB空間供Apache, Tomcat和其他預設安裝的代理程式共享和分配記憶體。
假如預設安裝的代理程式佔用200M記憶體;Tomcat的堆空間設定-Xmx
為600M,如下圖所示,Tomcat總佔用量將725M (持久代 + 本地堆空間)。Apache可使用的空間為剩下的700M。
圖 2:Top命令的截圖
對於Apache的700M記憶體,該如何設定合理的MaxClients值?
當然這也取決於Apache載入的模組型別和數量。以NHN的Web服務為例,把Apache當作簡單的代理使用,根據上圖RES顯示,4M空間對於每個httpd程式來說已足夠使用。因此700M空間能設定的MaxClients
為175。
總結
可靠的服務配置要能夠在滿載的情況下降低系統停頓時間並能夠最大範圍的保證成功響應使用者請求。對於Java應用來說,必須要確認在Full GC引起的SWT情況下,系統的配置是否能夠提供足夠可靠的服務。
如果為了應對單純的請求增加和防止DDos攻擊,在不考慮記憶體使用的情況下把MaxClients
設定過大,那麼MaxClients
不但會失去作為流控的用途,反而會帶來更為嚴重的後果。
在這個案例中,解決問題的最優途徑是加大系統的記憶體,或者設定MaxClients
為175(上面的計算結果)以保證只有QPS超過175時才會出現503錯誤。
相關文章
- 從一次 FULL GC 卡頓談對服務的影響GC
- Minor GC、Major GC以及Full GC的介紹及對比GC
- JVM 系列文章之 Full GC 和 Minor GCJVMGC
- Full GC (Metadata GC Threshold)GC
- Java GC 專家系列3:GC調優實踐JavaGC
- 總結Minor GC、Full GC觸發條件GC
- jvm系列:Java GC 分析JVMJavaGC
- jvm hotspot的minor major full gc之間的關係,以及哪些情況下會觸發full gcJVMHotSpotGC
- 淺談對java-GC的理解JavaGC
- jvm系列(九):Java GC 分析JVMJavaGC
- 觸發JVM進行Full GC的情況及應對策略JVMGC
- Java GC 專家系列5:Java應用效能優化的原則JavaGC優化
- JVM垃圾回收——新生代,老年代,永久代,Minor GC,Full GCJVMGC
- JVM系列之GCJVMGC
- java GC CollectorJavaGC
- Java GC 的那些事(2)JavaGC
- Java GC 的那些事(1)JavaGC
- Java GC的那些事(2)JavaGC
- 無線安全設定對速度的影響
- eclipse設定檢視GC日誌和如何理解GC日誌EclipseGC
- 【4】JVM-GC設計思路分析JVMGC
- jvm系列(十):如何優化Java GC「譯」JVM優化JavaGC
- 深入理解JVM(4) : Java垃圾收集 (GC)JVMJavaGC
- GPFS Persistent Reserve 的設定對Oracle RAC 的影響Oracle
- Arraysize的設定以及對查詢效能的影響
- [Inside HotSpot] Serial垃圾回收器Full GCIDEHotSpotGC
- Java——GC(垃圾回收)JavaGC
- 定時收集gc事件的指令碼GC事件指令碼
- Java 效能調優:最佳化 GC 執行緒設定JavaGC執行緒
- java學習筆記-4 JVM垃圾回收(GC)Java筆記JVMGC
- JAVA GC日誌分析JavaGC
- 對一次 GC日誌的分析GC
- python的GCPythonGC
- EBS R12中設定JDK GC LOGJDKGC
- 從CLR GC到CoreCLR GCGC
- GCGC
- Java出現一個新的GC:LXRJavaGC
- Java 9 中的 GC 調優基礎JavaGC