我最近運維了一個網上的實時接口服務,最近常常出現Address already in use (Bind failed)的問題。java
很明顯是一個端口綁定衝突的問題,因而大概排查了一下當前系統的網絡鏈接狀況和端口使用狀況,發現是有大量time_wait的鏈接一直佔用着端口沒釋放,致使端口被佔滿(最高的時候6w+個),所以HttpClient創建鏈接的時候會出現申請端口衝突的狀況。git
具體狀況以下:github
因而爲了解決time_wait的問題,網上搜索了些許資料加上本身的思考,因而認爲能夠經過鏈接池來保存tcp鏈接,減小HttpClient在併發狀況下隨機打開的端口數量,複用原來有效的鏈接。可是新的問題也由鏈接池的設置引入了。面試
在估算鏈接池最大鏈接數的時候,參考了業務高峯期時的請求量爲1分鐘1.2w pv,接口平響爲1.3s(複雜的廣告推廣效果模擬系統,在這種場景平響高是業務所需的緣由)。spring
所以qps爲12000*1.3\60=260後端
而後經過觀察了業務日誌,每次鏈接創建耗時1.1s左右, 再留70%+的上浮空間(怕鏈接數設置小出系統故障),最大鏈接數估計爲2601.1*1.7約等於500。xcode
爲了減小對以前業務代碼最小的改動,保證優化的快速上線驗證,仍然使用的是HttpClient3.1 的MultiThreadedHttpConnectionManager,而後在線下手寫了多線程的測試用例,測試了下併發度確實能比沒用線程池的時候更高,而後先在咱們的南京機房小流量上線驗證效果,效果也符合預期以後,就開始整個北京機房的轉全。結果轉全以後就出現了意料以外的系統異常。。。網絡
在當天晚上流量轉全以後,一塊兒狀況符合預期,可是到了次日早上就看到用戶羣和相關的運維羣裏有一些人在反饋實況頁面打不開了。這個時候我在路上,讓值班人幫忙先看了下大概的狀況,定位到了耗時最高的部分正是經過鏈接池調用後端服務的部分,因而能夠把這個突發問題的排查思路大體定在圍繞線程池的故障來考慮了。多線程
因而等我到了公司,首先觀察了一下應用總體的狀況:併發
因爲發現了有近 1/3的實例進程崩潰,而業務流量沒變,因爲RPC服務對provider的流量進行負載均衡,因此引起單臺機器的流量升高,這樣會致使後面的存活實例更容易出現崩潰問題,因而高優看了進程掛死的緣由。
因爲極可能是修改了HttpClient鏈接方式爲鏈接池引起的問題,最容易引發變化的確定是線程和CPU狀態,因而當即排查了線程數和CPU的狀態是否正常
一、CPU狀態
如圖可見Java進程佔用cpu很是高,是平時的近10倍
二、線程數監控狀態:
圖中能夠看到多個機器大概在10點初時,出現了線程數大量飆升,甚至超出了虛擬化平臺對容器的2000線程數限制(平臺爲了不機器上的部分容器線程數太高,致使機器總體夯死而設置的熔斷保護),所以實例是被虛擬化平臺kill了。以前爲何以前在南京機房小流量上線的時候沒出現線程數超限的問題,應該和南京機房流量較少,只有北京機房流量的1/3有關。
接下來就是分析線程數爲啥會快速積累直至超限了。這個時候我就在考慮是不是鏈接池設置的最大鏈接數有問題,限制了系統鏈接線程的併發度。爲了更好的排查問題,我回滾了線上一部分的實例,因而觀察了下線上實例的 tcp鏈接狀況和回滾以後的鏈接狀況
回滾以前tcp鏈接狀況:
回滾以後tcp鏈接狀況:
發現鏈接線程的併發度果真小不少了,這個時候要再確認一下是不是鏈接池設置致使的緣由,因而將沒回滾的機器進行jstack了,對Java進程中分配的子線程進行了分析,總於能夠確認問題
jstack狀態:
從jstack的日誌中能夠很容易分析出來,有大量的線程在等待獲取鏈接池裏的鏈接而進行排隊,所以致使了線程堆積,所以平響上升。因爲線程堆積越多,系統資源佔用越厲害,接口平響也會所以升高,更加重了線程的堆積,所以很容易出現惡性循環而致使線程數超限。
那麼爲何會出現併發度設置太小呢?以前已經留了70%的上浮空間來估算併發度,這裏面一定有蹊蹺!
因而我對源碼進行了解讀分析,發現了端倪:
如MultiThreadedHttpConnectionManager源碼可見,鏈接池在分配鏈接時調用的doGetConnection方法時,對可否得到鏈接,不只會對我設置的參數maxTotalConnections進行是否超限校驗,還會對maxHostConnections進行是否超限的校驗。
因而我馬上網上搜索了下maxHostConnections的含義:每一個host路由的默認最大鏈接,須要經過setDefaultMaxConnectionsPerHost來設置,不然默認值是2。
因此並非我對業務的最大鏈接數計算失誤,而是由於不知道要設置DefaultMaxConnectionsPerHost而致使每一個請求的Host併發鏈接數只有2,限制了線程獲取鏈接的併發度(因此難怪剛纔觀察tcp併發度的時候發現只有2個鏈接創建 😃 )
到此此次雪崩事件的根本問題已完全定位,讓咱們再次精煉的總結一下這個案件的全過程:
- 鏈接池設置錯參數,致使最大鏈接數爲2
- 大量請求線程須要等待鏈接池釋放鏈接,出現排隊堆積
- 夯住的線程變多,接口平響升高,佔用了更多的系統資源,會加重接口的耗時增長和線程堆積
- 最後直至線程超限,實例被虛擬化平臺kill
- 部分實例掛死,致使流量轉移到其餘存活實例。其餘實例流量壓力變大,容易引起雪崩
關於優化方案與如何避免此類問題再次發生,我想到的方案有3個:
如下是我設計的一個壓測方案:
a. 測試不用鏈接池和使用鏈接池時,分析總體能承受的qps峯值和線程數變化
b. 對比setDefaultMaxConnectionsPerHost設置和不設置時,分析總體能承受的qps峯值和線程數變化
c. 對比調整setMaxTotalConnections,setDefaultMaxConnectionsPerHost 的閾值,分析總體能承受的qps峯值和線程數變化
d. 重點關注壓測時實例的線程數,cpu利用率,tcp鏈接數,端口使用狀況,內存使用率
綜上所述,一次鏈接池參數致使的雪崩問題已經從分析到定位已所有解決。在技術改造時咱們應該要謹慎對待升級的技術點。在出現問題後,要重點分析問題的特徵和規律,找到共性去揪出根本緣由。
原文連接:https://blog.csdn.net/qq_16681169/article/details/94592472
版權聲明:本文爲CSDN博主「zxcodestudy」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處連接及本聲明。
近期熱文推薦:
1.1,000+ 道 Java面試題及答案整理(2021最新版)
2.終於靠開源項目弄到 IntelliJ IDEA 激活碼了,真香!
3.阿里 Mock 工具正式開源,幹掉市面上全部 Mock 工具!
4.Spring Cloud 2020.0.0 正式發佈,全新顛覆性版本!
以爲不錯,別忘了隨手點贊+轉發哦!