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