HttpClient參數配錯了,服務雪崩了。。。

點擊上方藍色字體,選擇“設爲星標”

回覆”學習資料“獲取學習寶典


一. 事件背景

我最近運維了一個網上的實時接口服務,最近經常出現Address already in use (Bind failed)的問題。

很明顯是一個端口綁定衝突的問題,於是大概排查了一下當前系統的網絡連接情況和端口使用情況,發現是有大量time_wait的連接一直佔用着端口沒釋放,導致端口被佔滿(最高的時候6w+個),因此HttpClient建立連接的時候會出現申請端口衝突的情況。

具體情況如下:

time_wait特徵

於是爲了解決time_wait的問題,網上搜索了些許資料加上自己的思考,於是認爲可以通過連接池來保存tcp連接,減少HttpClient在併發情況下隨機打開的端口數量,複用原來有效的連接。但是新的問題也由連接池的設置引入了。

二. 問題過程

在估算連接池最大連接數的時候,參考了業務高峯期時的請求量爲1分鐘1.2w pv,接口平響爲1.3s(複雜的廣告推廣效果模擬系統,在這種場景平響高是業務所需的原因)。

因此qps爲12000*1.3\60=260

然後通過觀察了業務日誌,每次連接建立耗時1.1s左右, 再留70%+的上浮空間(怕連接數設置小出系統故障),最大連接數估計爲2601.1*1.7約等於500。

爲了減少對之前業務代碼最小的改動,保證優化的快速上線驗證,仍然使用的是HttpClient3.1 的MultiThreadedHttpConnectionManager,然後在線下手寫了多線程的測試用例,測試了下併發度確實能比沒用線程池的時候更高,然後先在我們的南京機房小流量上線驗證效果,效果也符合預期之後,就開始整個北京機房的轉全。結果轉全之後就出現了意料之外的系統異常。。。

三. 案情回顧

在當天晚上流量轉全之後,一起情況符合預期,但是到了第二天早上就看到用戶羣和相關的運維羣裏有一些人在反饋實況頁面打不開了。這個時候我在路上,讓值班人幫忙先看了下大概的情況,定位到了耗時最高的部分正是通過連接池調用後端服務的部分,於是可以把這個突發問題的排查思路大致定在圍繞線程池的故障來考慮了。

於是等我到了公司,首先觀察了一下應用整體的情況:

  • 監控平臺的業務流量表現正常,但是部分機器的網卡流量略有突增
  • 接口的平響出現了明顯的上升
  • 業務日誌無明顯的異常,不是底層服務超時的原因,因此平響的原因肯定不是業務本身
  • 發現30個機器實例竟然有9個出現了掛死的現象,其中6個北京實例,3個南京實例

四. 深入排查

由於發現了有近 1/3的實例進程崩潰,而業務流量沒變,由於RPC服務對provider的流量進行負載均衡,所以引發單臺機器的流量升高,這樣會導致後面的存活實例更容易出現崩潰問題,於是高優看了進程掛死的原因。

由於很可能是修改了HttpClient連接方式爲連接池引發的問題,最容易引起變化的肯定是線程和CPU狀態,於是立即排查了線程數和CPU的狀態是否正常

1、CPU狀態

CPU特徵

如圖可見Java進程佔用cpu非常高,是平時的近10倍

2、線程數監控狀態:

圖片
圖片

圖中可以看到多個機器大概在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個連接建立 😃 )

五. 案情總結

到此這次雪崩事件的根本問題已徹底定位,讓我們再次精煉的總結一下這個案件的全過程:

  1. 連接池設置錯參數,導致最大連接數爲2
  2. 大量請求線程需要等待連接池釋放連接,出現排隊堆積
  3. 夯住的線程變多,接口平響升高,佔用了更多的系統資源,會加劇接口的耗時增加和線程堆積
  4. 最後直至線程超限,實例被虛擬化平臺kill
  5. 部分實例掛死,導致流量轉移到其他存活實例。其他實例流量壓力變大,容易引發雪崩

關於優化方案與如何避免此類問題再次發生,我想到的方案有3個:

  1. 在做技術升級前,要仔細熟讀相關的官方技術文檔,最好不要遺漏任何細節
  2. 可以在網上找其他可靠的開源項目,看看別人的優秀的項目是怎麼使用的。比如github上就可以搜索技術關鍵字,找到同樣使用了這個技術的開源項目。要注意挑選質量高的項目進行參考
  3. 先在線下壓測,用控制變量法對比各類設置的不同情況,這樣把所有問題在線下提前暴露了,再上線心裏就有底了

以下是我設計的一個壓測方案:

a. 測試不用連接池和使用連接池時,分析整體能承受的qps峯值和線程數變化

b. 對比setDefaultMaxConnectionsPerHost設置和不設置時,分析整體能承受的qps峯值和線程數變化

c. 對比調整setMaxTotalConnections,setDefaultMaxConnectionsPerHost 的閾值,分析整體能承受的qps峯值和線程數變化

d. 重點關注壓測時實例的線程數,cpu利用率,tcp連接數,端口使用情況,內存使用率

綜上所述,一次連接池參數導致的雪崩問題已經從分析到定位已全部解決。在技術改造時我們應該要謹慎對待升級的技術點。

在出現問題後,要重點分析問題的特徵和規律,找到共性去揪出根本原因。

原文鏈接:https://blog.csdn.net/qq_16681169/article/details/94592472


    
    
    

後臺回覆 學習資料 領取學習視頻


如有收穫,點個在看,誠摯感謝

本文分享自微信公衆號 - 猿天地(cxytiandi)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章