java压测请求线程数_程序员撕开京东 618 大促压测的另一面 | 原力计划
作者 | 天涯淚小武
責(zé)編 | 王曉曼
出品 | CSDN博客
前天618大促演練進(jìn)行了全鏈路壓測,在此之前剛好我的熱key探測框架也已經(jīng)上線灰度一周了,小范圍上線了幾千臺(tái)服務(wù)器,每秒大概接收幾千個(gè)key探測,每天大概幾億左右,因?yàn)榱亢苄?#xff0c;所以框架表現(xiàn)穩(wěn)定。借著這次壓測,剛好可以檢驗(yàn)一下熱key框架在大流量時(shí)的表現(xiàn)。畢竟作為一個(gè)新的中間件,里面很多東西還是第一次用,免不得會(huì)出一些問題。
壓測期,我沒有去擴(kuò)容熱key的worker集群,還是平時(shí)用的3個(gè)16C+1個(gè)4C8G的組合,3個(gè)16核是是主力,4核的是看上限能到什么樣。
由于之前那一周的平穩(wěn)表現(xiàn),導(dǎo)致我有點(diǎn)大意了,沒再去好好檢查代碼。導(dǎo)致實(shí)際壓測期間表現(xiàn)有點(diǎn)慘淡。
框架的架構(gòu)如下:
大概0點(diǎn)多壓測開始,初始量比較小,從10w/s開始?jí)?#xff0c;當(dāng)然都是壓的APP的后臺(tái),我的框架只是被動(dòng)的接收后臺(tái)發(fā)來的熱key探測請(qǐng)求而已。我主要檢測的就是worker集群,也就是那4臺(tái)機(jī)器的情況。
從壓測開始的一瞬間,那臺(tái)4核8G的機(jī)器就CPU100%,16核的CPU在90%以上,4核的100%即便在壓測暫停的間隙也沒有恢復(fù),一直都是100%,無論是10w/s,還是后期到大幾十萬/s。16核的在20w/s以上時(shí)也開始CPU100%,整體卡到不行了已經(jīng),連10秒一次的定時(shí)任務(wù)都卡的不走了,導(dǎo)致定時(shí)注冊自己到etcd的任務(wù)都停了,再導(dǎo)致etcd里把自己注冊信息過期刪除,大量和Client斷連。
然后Dashboard控制臺(tái)監(jiān)聽etcd熱key信息的監(jiān)聽器也出了大問題,熱key產(chǎn)生非常密集,導(dǎo)致Dashboard將熱key入庫卡頓,甚至于入庫時(shí),都已經(jīng)過期1分鐘多了,導(dǎo)致插入數(shù)據(jù)庫的時(shí)間全部是錯(cuò)的。
雖然Worker問題蠻多,也蠻嚴(yán)重,但好在etcd集群穩(wěn)如老狗,除了1分鐘一次的熱key密集過期導(dǎo)致CPU有個(gè)小尖峰,別的都非常穩(wěn)定,接收、推送都很穩(wěn),Client端表現(xiàn)也可以,沒有什么異常表現(xiàn)。
其中etcd真的很不錯(cuò),比想象中的更好,有圖為證:
Worker呢就是這樣子
后來經(jīng)過一系列操作,我還樂觀的修改上線了一版,然后沒什么用,在100%上穩(wěn)的一匹。
后來經(jīng)過我一天的研究分析,發(fā)現(xiàn)當(dāng)時(shí)沒找到關(guān)鍵點(diǎn),改的效果不明顯。當(dāng)然后來我自我感覺找到問題點(diǎn)了,又修改了一些,有待下次壓測檢驗(yàn)。
這一篇就是針對(duì)各個(gè)發(fā)現(xiàn)的問題進(jìn)行總結(jié),包括壓測期間的和之前灰度期間發(fā)現(xiàn)的一些。總的來說,無論書上寫的、博客寫的,各路這個(gè)說的那個(gè)說的雖然在本地跑的時(shí)候各種正常,但真正在大流量面前,未必能對(duì)。還有一些知名框架,參數(shù)配不好,效果未必達(dá)到預(yù)期。
平時(shí)發(fā)現(xiàn)的問題列表
先說壓測前小流量時(shí)的問題
1、在Worker端,會(huì)密集收到Client發(fā)來的請(qǐng)求。其中有代碼邏輯為先后取系統(tǒng)時(shí)間戳,居然有后取的時(shí)間戳小于前面的時(shí)間戳的情況(罕見、不能復(fù)現(xiàn)),猜測為Docker時(shí)間對(duì)齊問題。造成時(shí)間戳相減為負(fù)值,代碼數(shù)組越界,CPU瞬間達(dá)到100%。注意,這可是單線程的!
解決:問題雖然很奇葩,但很好解決,為負(fù)時(shí),按0處理。
2、使用網(wǎng)上找的Netty自定義協(xié)議(我前幾天還轉(zhuǎn)過那篇問題),在本地測試以及線上灰度測試時(shí),表現(xiàn)穩(wěn)健。但在全量上線后,2千臺(tái)client情況下,出現(xiàn)過單Worker關(guān)閉一段時(shí)間并重啟后,瞬間收到高達(dá)數(shù)GB的流量包,直接打爆內(nèi)存,導(dǎo)致Worker停機(jī),后續(xù)無法啟動(dòng)的情況。
解決:書上及網(wǎng)上均未找到相關(guān)解決方案,類似場景別人極難遇到。后通過使用Netty官方自帶協(xié)議StringDecoder加分隔符后,未復(fù)現(xiàn)突傳大包的情況,目前線上表現(xiàn)穩(wěn)定。
3、Netty client是可以反復(fù)連接同一個(gè)server的,造成單個(gè)Client對(duì)單個(gè)Server產(chǎn)生多個(gè)長連接的情況,使得Server的TCP連接數(shù)遠(yuǎn)遠(yuǎn)大于Client的總數(shù)量。此前書上、網(wǎng)絡(luò)教程等各個(gè)地方均未提及該情況。使得誤認(rèn)為,Client對(duì)Server僅會(huì)保持一個(gè)長連接。
解決:對(duì)Client的連接進(jìn)行排重、加鎖,避免Client反復(fù)連接同一個(gè)server。
4、Netty server在推送信息到大量client時(shí),會(huì)造成CPU瞬間飆升至60-100%,推送完畢后CPU下降至正常值.
解決:在推送時(shí),避免做循環(huán)體內(nèi)Json序列化操作,應(yīng)在循環(huán)體外進(jìn)行.
5、在復(fù)用Netty創(chuàng)建出來的ByteBuf對(duì)象時(shí),反復(fù)的使用它,會(huì)出現(xiàn)大量的報(bào)錯(cuò)。原因是對(duì)ByteBuf對(duì)象了解不深,該對(duì)象和普通的Java對(duì)象不一樣,Java對(duì)象是可以傳遞下去反復(fù)使用的,不存在使用后銷毀的情況,而ByteBuf在被Netty傳出去后,就銷毀了,里面攜帶的字節(jié)組就沒了。
解決:每次都創(chuàng)建新的ByteBuf對(duì)象,不要復(fù)用它。
6、2千臺(tái)Client在監(jiān)聽到Worker發(fā)生變化后,會(huì)同時(shí)瞬間去連接它,和平時(shí)上線時(shí),每次幾百臺(tái)緩慢連接Server的場景不同,突發(fā)瞬間數(shù)千連接時(shí),可能發(fā)生Server丟失一部分連接,導(dǎo)致部分Client連接失敗。
解決:不再采用監(jiān)聽的方式,而采用定時(shí)輪訓(xùn)的方式,錯(cuò)開連接的時(shí)機(jī),對(duì)連不上的Worker進(jìn)行本地保存,后加一個(gè)定時(shí)任務(wù),定時(shí)去連接那些沒連上的Server。
7、Worker機(jī)器占用的內(nèi)存持續(xù)增長,超過給Docker分配的內(nèi)存后,被系統(tǒng)殺死進(jìn)程。
解決:Worker全部是部署在Docker里的,剛開始我是沒有給它配JVM參數(shù)的,譬如那個(gè)4核8G的,我只是將它部署上去,就沒有管它了。隨后,它的內(nèi)存在持續(xù)穩(wěn)定上漲,從未下降。直到內(nèi)存爆滿。后來經(jīng)進(jìn)入到容器內(nèi)部,執(zhí)行查看內(nèi)存命令,發(fā)現(xiàn)雖然Docker是4核8G的,但是宿主機(jī)是250G的。JVM采用默認(rèn)的內(nèi)存分配策略,初始分配1/64的內(nèi)存,最大分配1/4的內(nèi)存。但是是按250G進(jìn)行分配的,導(dǎo)致JVM不斷擴(kuò)容再擴(kuò)容,直到1/4 * 250G,在到達(dá)Docker分配的8G時(shí)就被殺死了。后來給容器配置了JVM參數(shù)后,內(nèi)存平穩(wěn)。這塊帶來的經(jīng)驗(yàn)教訓(xùn)就是,一定要給自己的程序配JVM,不然JVM按默認(rèn)的執(zhí)行,后果就不可控了。
壓測發(fā)現(xiàn)的問題列表
前面發(fā)現(xiàn)的多是代碼邏輯和配置問題,壓測期間主要是CPU100%的問題,也列一下。1 、Netty線程數(shù)巨多、disruptor線程數(shù)也巨多,導(dǎo)致CPU100%
問題描述:Worker部署的JDK版本是1.8.20,注意,這個(gè)版本是在1.8里算比較老的版本。Worker里面作為Netty Server啟動(dòng),我是沒有給它配線程池的(如圖,之前Boss和Worker我都沒有指定線程數(shù)量),所以它走的就是默認(rèn)Runtime.getRuntime.availableProcessors* 2。這個(gè)是系統(tǒng)獲取核數(shù)的代碼,在JDK1.8.31之前,Docker容器內(nèi)的這段代碼獲取到的是宿主機(jī)的核數(shù),而非給容器分配的核數(shù)!!!譬如我的程序取到的就是32核,而非分配的4核。再乘以2后,變成了64個(gè)線程。導(dǎo)致Netty boss和worker線程數(shù)高達(dá)64,另外我還用了Disruptor,Disruptor的Consumer數(shù)量也是64!導(dǎo)致壓測一開始,瞬間CPU切換及其繁忙,大量的空轉(zhuǎn)。大家都知道,CPU密集型的應(yīng)用,線程數(shù)最好比較小,等于核數(shù)是比較合適的,而我的程序線程數(shù)高達(dá)180,CPU全部用于輪轉(zhuǎn)了。
之后我增加了判斷JDK版本的邏輯,JDK1.8.31后的獲取到的AvailableProcessors就是對(duì)的了,并且我限制了BossGroup的線程數(shù)為1.再次上線后,CPU明顯有下降!
帶來的經(jīng)驗(yàn)教訓(xùn)是,用Docker時(shí),需要注意JDK版本,尤其是有獲取系統(tǒng)核數(shù)的代碼作為邏輯時(shí)。CPU密集型的,切勿搞很多線程。
2、CPU持續(xù)100%,導(dǎo)致定時(shí)任務(wù)都不執(zhí)行了
和第一個(gè)問題是連鎖的,因?yàn)閣orker接收到的請(qǐng)求非常密集,每秒達(dá)10萬以上,而CPU已經(jīng)全部用于N個(gè)線程的輪轉(zhuǎn)了,真正工作的都沒了,我的一個(gè)很輕的定時(shí)任務(wù)5s上傳一次Worker自己的IP信息到配置中心etcd,連這個(gè)定時(shí)任務(wù)都工作不ok了,通過Jstack查看,一直處于Wait狀態(tài)。之后導(dǎo)致etcd里該Worker信息過期被刪除,再導(dǎo)致2千多個(gè)Client從etcd沒取到該Worker注冊信息,就把它給刪掉了,發(fā)生了大量client沒有和Worker進(jìn)行連接。
可見,CPU滿時(shí),什么都不靠譜了,核心功能都會(huì)阻塞。
3、Caffeine密集擴(kuò)容,耗費(fèi)CPU大
因?yàn)閃orker里是用Caffeine來存儲(chǔ)各Client發(fā)來的Key信息的,之后讀取Caffeine進(jìn)行存取。Caffeine底層是用ConcurrentHashMap來進(jìn)行的數(shù)據(jù)存儲(chǔ),大家都知道HashMap擴(kuò)容的事,擴(kuò)容2倍,就要進(jìn)行一次Copy,里面動(dòng)輒幾十萬個(gè)Key,擴(kuò)容Resize時(shí),CPU會(huì)占用比較大。尤其是CPU本身負(fù)荷很重時(shí),這一步也會(huì)卡住。
我的Worker給Caffeine分配的最大500萬容量,雖然不是很大,但卡頓時(shí),Resize這一步執(zhí)行很慢。不過這個(gè)不是什么大問題,也沒有什么好修復(fù)的,就保持這樣就行。
4、Caffeine在密集失效時(shí),老版本JDK下,Caffeine默認(rèn)的ForkJoinPool有BUG
Caffeine我是設(shè)置的寫入后一分鐘過期,因?yàn)槭敲芗瘜懭?#xff0c;自然也會(huì)密集失效。Caffeine采用線程池進(jìn)行過期刪除,不指定線程池時(shí)采用默認(rèn)的ForkJoinPool。問題是什么呢,大家自己也能試出來。搞個(gè)死循環(huán)往Caffeine里寫值,然后看它的失效。在JDK1.8.20之前,這個(gè)ForkJoinPool存在不提交任務(wù)的BUG,導(dǎo)致Key過期后未被刪除。進(jìn)而Caffeine容量爆滿超過閾值,發(fā)生內(nèi)存溢出。架構(gòu)師針對(duì)該問題給Caffeine官方提了Issue,對(duì)方回復(fù),請(qǐng)勿過于密集寫入Caffeine,寫入過快時(shí),刪除跟不上。還需要升級(jí)JDK,至少要超過1.8.20.不然ForkJoinPool也有問題。
5、Disruptor消費(fèi)慢大名鼎鼎的Disruptor實(shí)際表現(xiàn)并不如名氣那么好,很多文章都是在講Disruptor怎么怎么牛x,一秒幾百萬。在Worker里的用法是這樣的,Netty的Worker線程池接收到請(qǐng)求后,統(tǒng)一全部發(fā)到Disruptor里,然后我搞CPU核數(shù)個(gè)線程來消費(fèi)這些請(qǐng)求,做計(jì)算熱Key數(shù)量的操作。而壓測期間,CPU100%時(shí),幾乎所有的線程都卡在了Disruptor生產(chǎn)上。即N個(gè)線程在這個(gè)生產(chǎn)者獲取Next序列號(hào)時(shí)卡住,原因很簡單,就是沒消費(fèi)完,生產(chǎn)者阻塞。我設(shè)置的Disruptor的隊(duì)列長度為100萬,實(shí)際應(yīng)該寫不滿這個(gè)隊(duì)列,但不知道為什么還是大量卡在了這個(gè)地方。該問題有待下次壓測時(shí)檢驗(yàn)。
6、有個(gè)定時(shí)任務(wù)里面有耗大量CPU的方法
之前為了統(tǒng)計(jì)Caffeine的容量和占用的內(nèi)存,我搞了個(gè)定時(shí)任務(wù)10秒一次上傳Caffeine的內(nèi)存占用。就是被注釋掉的那行,上線后坑到我了,那一句特別耗CPU。趕緊刪掉,避免這種測試性質(zhì)的代碼誤上線,占用大量資源。
7、數(shù)據(jù)庫寫入速度跟不上熱Key產(chǎn)生的速度
我是有個(gè)地方在監(jiān)聽etcd里熱Key的,每當(dāng)有新Key產(chǎn)生時(shí),就會(huì)往數(shù)據(jù)庫里插值。結(jié)果由于Key瞬間來了好幾千個(gè),數(shù)據(jù)庫處理不過來,導(dǎo)致大量的阻塞,等輪到這條Key信息插入時(shí),早就已經(jīng)過期了,造成數(shù)據(jù)庫里的數(shù)據(jù)全是錯(cuò)的。
這個(gè)問題比較好解決,可以做批量入庫,加個(gè)隊(duì)列緩沖就好了。
初步總結(jié)
其實(shí)里面有很多本地永遠(yuǎn)無法出現(xiàn)的問題,譬如時(shí)間戳的那個(gè),還有一些問題是JDK版本的,還有是Docker的。但最終都可以歸納為,代碼不夠嚴(yán)謹(jǐn),沒有充分考慮到這些可能會(huì)帶來問題的地方,譬如不配JVM參數(shù)。
但是不上線又怎么都測試不出來這些問題,或者上線了量級(jí)不夠時(shí)也發(fā)現(xiàn)不了。這就說明一個(gè)穩(wěn)定健壯的中間件,是需要打磨的,不是說書上抄了一段Netty的代碼,上線了它就能正常運(yùn)行了。
當(dāng)然進(jìn)步的過程其實(shí)就是踩坑的過程,有了相應(yīng)的場景,實(shí)實(shí)在在的并發(fā)量,踩過足夠的坑,才能打磨好一個(gè)框架。
也希望有相關(guān)應(yīng)用場景的同學(xué),關(guān)注京東熱Key探測框架。
版權(quán)聲明:本文為CSDN博主「天涯淚小武」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權(quán)協(xié)議,轉(zhuǎn)載請(qǐng)附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/tianyaleixiaowu/article/details/106092060
?一加回應(yīng)“透視”問題;劉強(qiáng)東內(nèi)部信重新定義京東;Apache Dubbo 2.7.7 發(fā)布 | 極客頭條
?平安科技王健宗:所有 AI 前沿技術(shù),都可以在聯(lián)邦學(xué)習(xí)中大展身手!
?踢翻這碗狗糧:程序員花 7 個(gè)月敲出 eBay,只因女票喜歡糖果盒!
?我佛了!用KNN實(shí)現(xiàn)驗(yàn)證碼識(shí)別,又 Get 到一招!
?如何使用 SQL Server FILESTREAM 存儲(chǔ)非結(jié)構(gòu)化數(shù)據(jù)?這篇文章告訴你!
?加密價(jià)格更新周期:看似雜亂無章,實(shí)際內(nèi)藏玄機(jī)
《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的java压测请求线程数_程序员撕开京东 618 大促压测的另一面 | 原力计划的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python绘制函数怎么去掉原点_pyt
- 下一篇: visionmaster视觉软件说明书_