? ? ? ?不久前參與開發(fā)了一個基于dubbo分布式框架的底層賬單系統(tǒng),并實現(xiàn)了其中的一部分業(yè)務(wù)接口,目前需對這些接口進行壓測,以評估生產(chǎn)環(huán)境所能承受的最大吞吐量。筆者以其中一個查詢接口為例來回顧此次壓測的整體流程
壓測準(zhǔn)備:
1.調(diào)用查詢接口的測試jar包,作為dubbo-consumer,依賴了查詢服務(wù)的api,測試module基于maven開發(fā),執(zhí)行maven clean package即可通過編譯得到j(luò)ar包
2.JMeter:Apache組織開發(fā)的基于Java的壓力測試工具
方案:
無限次請求查詢接口(保證任意時刻并發(fā)量相同),觀察Error%為0,當(dāng)請求平穩(wěn)進行時的tps,為該接口吞吐量
實施:
1.JMeter中添加一個測試計劃,線程組容量分別設(shè)為10、20、50、80、100、200、400、1000、2000,通過jmeter csv data set config設(shè)置三組查詢參數(shù)
2.準(zhǔn)備完畢后,依次在不同容量線程組下啟動測試計劃,結(jié)果如下



? ? ? ?結(jié)論:當(dāng)線程數(shù)為200時,tps達到1700+,隨著線程數(shù)增加,99%Line明顯躥升至6s,猜想部分線程請求不到資源,并且Error線程占比瞬間增多也印證了這一點。ps:如果同一組參數(shù)測試,壓測效果卻在遞減,可嘗試重啟Jmeter。
思考&決策:
? ? ? ?當(dāng)前測試結(jié)構(gòu)中包含三個節(jié)點:本地測試Consumer節(jié)點—>查詢接口Provider節(jié)點—>數(shù)據(jù)庫節(jié)點,所以相鄰兩個節(jié)點間均可能產(chǎn)生并發(fā)瓶頸,所以需要定位具體問題發(fā)生的具體位置。由于壓測僅需一個節(jié)點,所以筆者使用了jVisualVM+jmx+jstacd組合,遠(yuǎn)程監(jiān)聽Dubbo服務(wù)所在的那臺機器。
調(diào)優(yōu)準(zhǔn)備:
1.jstatd:(JDK自帶)基于RMI的服務(wù)程序,用于監(jiān)控基于HotSpot的JVM中資源的創(chuàng)建及銷毀。首次使用需在被監(jiān)控機器中加入權(quán)限授予文件jstatd.all.policy(jdk的bin目錄下)
文件內(nèi)容:
grant codebase"file:${java.home}/../lib/tools.jar"{
permission?java.security.AllPermission;
};
完畢后執(zhí)行./jstatd -J-Djava.security.policy=jstatd.all.policy -J-Djava.rmi.server.hostname=遠(yuǎn)程服務(wù)器ip &
對外默認(rèn)開啟1099端口
2.jVisualVM:(JDK自帶)Java性能分析工具
3.jmx:(JDK自帶),是一個為應(yīng)用程序、設(shè)備、系統(tǒng)等植入管理功能的框架,如管理基于tomcat的web服務(wù),本文中管理基于SpringBoot的Dubbo服務(wù),需在啟動腳本中加入jmx的啟動配置
-Dcom.sun.management.jmxremote
-Djava.rmi.server.hostname=遠(yuǎn)程服務(wù)器ip
-Dcom.sun.management.jmxremote.port=18999(自定義)
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.authenticate=false
方案&實施:
開啟壓測,并觀察jVisualVM中占用CPU時間非常多的熱點方法,并查詢遠(yuǎn)程主機cpu使用率情況

? ? ? ?發(fā)現(xiàn)在正常線程數(shù)請求時,獲取DriudDataSource連接池連接的方法CPU時間非常高,經(jīng)查詢發(fā)現(xiàn),系統(tǒng)中連接池的配置:initialSize、minIdle、maxActive都非常低,遂進行了第一次調(diào)優(yōu):提升數(shù)據(jù)庫連接數(shù),連接池初始化連接數(shù)50,最小空閑連接數(shù)50,最高活躍連接數(shù)400。
? ? ? ?提升后,獲取連接方法的CPU時間明顯降低,遂測試線程數(shù)為400時的請求環(huán)境下的支持情況,發(fā)現(xiàn)已經(jīng)開始出現(xiàn)error,即一部分線程請求不到資源,99%Line也達到6s之大!
分析:
? ? ? ?此時系統(tǒng)的數(shù)據(jù)庫連接池配置已經(jīng)達到400,瓶頸不在此處,那么會不會是遠(yuǎn)程的數(shù)據(jù)庫節(jié)點存在瓶頸,于是遠(yuǎn)程登錄數(shù)據(jù)庫節(jié)點,發(fā)現(xiàn)mysql的允許連接數(shù)非常大,不存在瓶頸。既然請求線程數(shù)非常大,數(shù)據(jù)庫連接池連接數(shù)非常大,數(shù)據(jù)庫提供的連接數(shù)也足夠,CPU、JVM均沒有異常,那么造成性能瓶頸的可能在與dubbo允許提供的連接線程數(shù)不足以匹配壓測產(chǎn)生的線程數(shù)。
? ? ? ?定位到dubbo配置,發(fā)現(xiàn)并沒有顯式定義dubbo連接數(shù),查閱dubbo開發(fā)文檔

? ? ? ?問題發(fā)現(xiàn)了:dubbo默認(rèn)連接線程數(shù)為100, ?而并發(fā)量400的請求線程對dubbo造成的壓力過大,導(dǎo)致壓測不久就出現(xiàn)部分線程請求不到資源超時的問題,遂進行了第二次調(diào)優(yōu):提升Dubbo線程池連接數(shù),將連接數(shù)提升至1000。
? ? ? ? 那么是不是到此并發(fā)就不存在瓶頸了呢?1000請求線程+dubbo允許線程數(shù)1000+數(shù)據(jù)庫大連接數(shù)支持,理論上操作是沒有問題的,我們來實際跑一下,發(fā)現(xiàn)壓測時出現(xiàn)了更嚴(yán)重的問題,剛開始請求就出現(xiàn)了OOM及超過一半的error線程,準(zhǔn)備去遠(yuǎn)程機器打印一下執(zhí)行日志,就連tail及ps命令都沒有可用資源供執(zhí)行,停掉了請求線程,又費了九牛二虎之力停掉了服務(wù)進程,開始分析原因:各系統(tǒng)間通信均無瓶頸,問題會出在哪里,是什么原因撐爆了JVM,已知的條件是遠(yuǎn)程服務(wù)至少有1000個線程在服務(wù)器內(nèi)生存,是不是線程量太大撐爆了機器?由于JVM中,??臻g線程私有,查閱JVM參數(shù)

服務(wù)器為linux系統(tǒng),那默認(rèn)ThreadStackSize=1024K,那么1000個線程JVM就需要創(chuàng)建1000*1024k即1個G的空間!這個節(jié)點部署三個服務(wù),光一個服務(wù)的請求線程就占據(jù)1個G,內(nèi)存溢出也是情理之中的了,遂進行了第三次調(diào)優(yōu):減少線程??臻g,ThreadStackSize調(diào)至256K,也是夠用的,再次模擬1000線程并發(fā),OK,無論是系統(tǒng)間線程調(diào)用還是內(nèi)存中JVM空間都在正常情況下,并未出現(xiàn)線程請求不到資源的情況。
總結(jié):
? ? ? ?本次壓測主要目的是確定單節(jié)點在生產(chǎn)環(huán)境所能承受的tps峰值,并借助測試數(shù)據(jù)反向分析之前開發(fā)及單元測試無法覆蓋的隱藏問題,通過三次調(diào)優(yōu),我們可以發(fā)現(xiàn),該環(huán)境下瓶頸主要在系統(tǒng)間請求發(fā)生時,以及JVM自身無法負(fù)載大數(shù)據(jù)量線程導(dǎo)致。當(dāng)然也有可能發(fā)生在程序本身過程中,如邏輯中創(chuàng)造大量對象,消耗大量內(nèi)存,或同步邏輯處理塊設(shè)計欠缺,導(dǎo)致死鎖、線程餓死等。筆者所描述的問題只是眾多壓測問題中的一小部分,分析、操作難免有疏漏,歡迎各位同學(xué)予以指正及建議。感謝華哥、林哥指導(dǎo),感謝一鳴同學(xué)協(xié)助~