這是“成為Java GC專家系列文章”的第四篇。
在第一篇文章 成為JavaGC專家Part I — 深入淺出Java垃圾回收機(jī)制 中我們學(xué)習(xí)了不同GC算法的執(zhí)行過程,GC如何工作,新生代及老年代的基本概念,在JDK7中你應(yīng)該了解的5種GC類型以及他們的性能如何。
在第二篇文章 成為JavaGC專家Part II — 如何監(jiān)控Java垃圾回收機(jī)制 中我們學(xué)到了JVM到底是如何執(zhí)行垃圾回收,我們?nèi)绾伪O(jiān)控GC,以及那些工具可以使得監(jiān)控過程更高效。
在第三篇文章 成為Java GC專家系列Part III–如何優(yōu)化Java垃圾回收機(jī)制中我們通過實(shí)際的例子學(xué)到了一些可以優(yōu)化GC的參數(shù)。同時(shí)我們講解了如何減少對象被轉(zhuǎn)移到老年代空間,如何縮短Full GC時(shí)間,以及如何設(shè)置GC類型及內(nèi)存空間。
在第四篇文章中,我們將闡述Apache中MaxClients
參數(shù)的重要性,以及他如何在GC發(fā)生時(shí),顯著地影響整個(gè)系統(tǒng)的性能。我將提供幾個(gè)例子以方便你理解MaxClients
導(dǎo)致的問題。同時(shí)我還會說明如何根據(jù)系統(tǒng)的內(nèi)存情況,設(shè)置最佳的MaxClients
參數(shù)值。
NHN (譯者注:NHN是作者工作的公司)服務(wù)的執(zhí)行環(huán)境中存在一組Throttle valve-type參數(shù)(譯者注:節(jié)流閥參數(shù),用于控制系統(tǒng)負(fù)載)。這些參數(shù)對于系統(tǒng)來說十分重要。下面我們看一下Apache的 MaxClients
參數(shù)如何在Full GC 發(fā)生時(shí)影響系統(tǒng)。
大部分開發(fā)人員都知道在由于GC發(fā)生而導(dǎo)致的”停止世界現(xiàn)象(STW) “(詳細(xì)請參見Understanding Java Garbage Collection)。尤其是,NHN的Java開發(fā)人員經(jīng)常會遇到由于GC原因?qū)е碌腡omcat報(bào)錯。由于Java 虛擬機(jī) (JVM)管理著內(nèi)存,以Java為基礎(chǔ)的程序無法擺脫GC導(dǎo)致的STW現(xiàn)象。假如在某一個(gè)時(shí)間,當(dāng)你正在操作你開發(fā)的應(yīng)用時(shí),GC開始執(zhí)行。即使TTS錯誤沒有發(fā)生,你的服務(wù)也會給客戶展現(xiàn)未預(yù)期的503錯誤。
由于架構(gòu)本身的特點(diǎn),Web服務(wù)更適合相比較而言更適合橫向擴(kuò)展(譯者注:增加服務(wù)器的數(shù)量,而不是提高件配置)。因此,總體來講,物理設(shè)備會根據(jù)性能要求被配置成1臺Apache+n臺Tomcat。但是本文假設(shè)我們的環(huán)境是1臺Apache+一臺Tomcat同時(shí)安裝在一臺主機(jī)行,如下圖所示。
圖1:本文假射的服務(wù)執(zhí)行環(huán)境
僅供參考,本文描述的參數(shù)基于Apache 2.2.21 (prefork MPM),Tomcat 6.0.35,CentOS 4.72 (32-bit),jdk 1.6.0_24。
系統(tǒng)可用內(nèi)存2GB,垃圾收集器使用ParallelOldGC,AdaptiveSizePolicy采用默認(rèn)的設(shè)置true,堆內(nèi)存空間600M
讓我們假設(shè)訪問Apache的請求為 200 req/s且有10個(gè)httpd進(jìn)程在運(yùn)行,另外我們暫時(shí)不考慮每個(gè)請求的響應(yīng)時(shí)間。在這種前提下,我們假設(shè)由于full GC導(dǎo)致的暫停時(shí)間為1秒。當(dāng)Full GC發(fā)生的時(shí)候Tomcat會怎樣?
第一件進(jìn)入你腦海的事情應(yīng)該是Tomcat會因?yàn)閒ull GC而停止響應(yīng)任何請求。在這種情況下,當(dāng)Tomcat暫停相應(yīng)請求時(shí)Apache會發(fā)生什么?
當(dāng)Tomcat暫停時(shí),請求會以200 req/s的速度不斷的涌入Apache。一般來說,在Full GC發(fā)生之前,請求的響應(yīng)可以快速地被10個(gè)或更多的httpd進(jìn)程處理掉。但是,因?yàn)門omcat暫停了,httpd進(jìn)程會被不停地創(chuàng)建以相應(yīng)新進(jìn)請求。直到超過httpd.conf 文件中定義 MaxClients
為止。由于默認(rèn)值為256,Apache不會在乎請求以200 req/s的速度涌入。
這時(shí),新創(chuàng)建的httpd線程將如何呢?
Httpd進(jìn)程通過mod_jk 模塊所管理的空閑的AJP連接,將請求轉(zhuǎn)發(fā)給Tomcat。如果沒有空閑連接,他會申請創(chuàng)建新的連接。但是,因?yàn)門omcat暫停了,創(chuàng)建新連接的請求會被拒絕。因此這些請求會被存儲在backlog隊(duì)列中,數(shù)量的多少取決于server.xml中關(guān)于AJP Connector的設(shè)置。一旦請求數(shù)量超過backlog隊(duì)列的空間限制。Apache就會返回拒絕連接錯誤。并且返回HTTP 503 錯誤給用戶。
在這種假設(shè)條件下,默認(rèn)的backlog隊(duì)列空間是100,而請求到達(dá)速度是200 req/s。因此,full GC導(dǎo)致的一秒鐘的暫停會使得超過100個(gè)請求返回503錯誤。
這樣,當(dāng)Full GC結(jié)束后,backlog隊(duì)列中存儲的內(nèi)容會被Tomcat接受并在通過工作線程處理,線程的最大數(shù)量取決于MaxThreads
的值(默認(rèn)200)。
在這種情況下,設(shè)定哪個(gè)參數(shù)可以避免返回給用戶503錯誤呢?
首先,我們應(yīng)該知道backlog的值要夠大,以至于能夠容納所有因?yàn)镕ull GC導(dǎo)致暫停期間涌入的請求。換句話說太應(yīng)該不小于200。
那么,這么設(shè)置之后會不會產(chǎn)生新的問題呢?
讓我們假設(shè)將backlog設(shè)置為200后再重復(fù)一下上面的過程。得到的結(jié)果比之前更加嚴(yán)重。系統(tǒng)內(nèi)存使用量一般情況下為50%,但是,在發(fā)生Full GC時(shí)快速增加到100%,同時(shí)導(dǎo)致交換內(nèi)存空間快速增加,更為嚴(yán)重的是導(dǎo)致Full GC的暫停時(shí)間從1秒變成了4秒甚至更多,系統(tǒng)在此期間完全宕機(jī),不能響應(yīng)任何請求。
在第一種情況下,只有100或更多的請求返回503錯誤。但是,當(dāng)我們把backlog調(diào)整到200后,超過500個(gè)請求會掛起3秒甚至更多地時(shí)間無法得到應(yīng)答
上面這個(gè)例子可以很好的說明當(dāng)你沒有完全理解各個(gè)設(shè)置之間的內(nèi)在關(guān)系時(shí)(例如,對于系統(tǒng)的影響),盲目修改系統(tǒng)會導(dǎo)致什么后果。
那么,為什么會產(chǎn)生這個(gè)現(xiàn)象呢?
問題的根源在于 MaxClients
參數(shù)的特性。
將MaxClients
設(shè)置為一個(gè)很大的值本身沒有問題,但最重要的是在設(shè)定MaxClients
參數(shù)時(shí),你要確保即使等同于MaxClients
數(shù)量的httpd進(jìn)程被同時(shí)創(chuàng)建,內(nèi)存使用量也不會超過80%。
系統(tǒng)的內(nèi)存交換參數(shù)一般被設(shè)定為60(默認(rèn))。因此,當(dāng)內(nèi)存使用量超過80%時(shí),就會進(jìn)行內(nèi)存交換。
讓我們再來看一下為什么這個(gè)特性會導(dǎo)致上面那個(gè)嚴(yán)重的問題。當(dāng)請求以200 req/s的速度涌向Tomcat時(shí),Tomcat由于full GC暫停了。此時(shí)backlog被設(shè)置為200。Apache大約創(chuàng)建100個(gè)httpd進(jìn)程。在這種情況下,一旦內(nèi)存使用量超過80%,操作系統(tǒng)會激活交換內(nèi)存區(qū)域,并且由于系統(tǒng)認(rèn)為JVM的老年代中的對象在很長一段時(shí)間內(nèi)未被使用,而將他們移動到交換區(qū)域。
最終的結(jié)果是,GC使用了內(nèi)存交換空間,暫停時(shí)間劇增。因此httpd進(jìn)程數(shù)進(jìn)一步增加。從而導(dǎo)致上面描述的內(nèi)存使用量達(dá)到100%的情況。
這兩個(gè)場合的唯一區(qū)別就是backlog的值:100 vs.200。為什么只在200的情況下發(fā)生?
兩者不同的原因在于創(chuàng)建的httpd進(jìn)程的數(shù)量。當(dāng)backlog設(shè)置為100時(shí)并且Full GC發(fā)生時(shí),會創(chuàng)建100個(gè)請求的連接并保存在backlog隊(duì)列中。其他請求得到拒絕連接錯誤信息并發(fā)揮503錯誤。因此,總的httpd 進(jìn)程數(shù)量僅僅會略高于100。而當(dāng)backlog被設(shè)置為200時(shí),200個(gè)請求會創(chuàng)建連接,因此。總的httpd進(jìn)程數(shù)會多于200。這樣超過閥值,從而導(dǎo)致內(nèi)存交換的發(fā)生。緊接著,不考慮內(nèi)存使用量而的設(shè)定 MaxClients
參數(shù),F(xiàn)ull GC導(dǎo)致httpd進(jìn)程數(shù)量暴增,引發(fā)內(nèi)存交換,降低系統(tǒng)性能。
如果系統(tǒng)的內(nèi)存使2GB,MaxClients
的值在任何情況下都不應(yīng)該超過內(nèi)存的80%(1.6GB),以避免由于內(nèi)存交換導(dǎo)致的性能下降。換句話說。1.6GB的內(nèi)存應(yīng)該共享和分配給Apache,Tomcat以及那些默認(rèn)被安裝的代理程序。
讓我們假設(shè)代理程序被默認(rèn)安裝在系統(tǒng),并占用了200m內(nèi)存,對于Tomcat堆內(nèi)存的-Xmx
被設(shè)定為 600m。因此根據(jù)top命令的結(jié)果,Tomcat會一直占用725m(Perm Gen + Native Heap Area)。最終Apache可以使用700m內(nèi)存空間。如下所示。
圖2:測試系統(tǒng)的top截屏
如上所述,我們將內(nèi)存設(shè)為700m后MaxClients
應(yīng)該是多少呢?
這要取決于加載模塊的數(shù)量,對于NHN Web服務(wù)來說。Apache只是個(gè)簡單的代理轉(zhuǎn)發(fā),每個(gè)httpd線程4m內(nèi)存(根據(jù)top命令的結(jié)果)足以(參見圖2)。因此。700m內(nèi)存對應(yīng)的 MaxClients
應(yīng)該是175。
一個(gè)健壯的服務(wù)配置至少應(yīng)該能夠降低在服務(wù)過載時(shí)宕機(jī)的時(shí)間,在合理的范圍內(nèi)成功的應(yīng)答請求。針對基于Java的Web服務(wù)。你必須檢查你的服務(wù)在Full GC導(dǎo)致的STW時(shí)間內(nèi)能否穩(wěn)定的響應(yīng)請求。
為了響應(yīng)更多的用戶請求和應(yīng)對DDoS攻擊,在沒有全面考慮系統(tǒng)內(nèi)存等因素的情況下,貿(mào)然地將 MaxClients
設(shè)置為一個(gè)很大的值,那么它將失去作為閥值的功能,而導(dǎo)致系統(tǒng)出現(xiàn)更嚴(yán)重的問題。
本文提到的情況只會持續(xù)3-5秒,因此絕大多數(shù)傳統(tǒng)的監(jiān)控工具都無法及時(shí)的發(fā)現(xiàn)。
作者 Dongsoon Choi 高級工程師@Game Service Technical Support Team, NHN Corporation.