HttpClient異步調用引發的程序掛起問題排查及解決


      在搭建搭建分布式系統時,基礎組件與框架的重要性不言而喻。但是如果組件出現bug,真的很要命。雖然我們通過各種單元測試,拼命找bug,但是總有一些問題被盲目自信蒙蔽了雙眼,很多時候我們認為這段代碼100%沒有問題,但是我想說,沒有100%沒有問題的代碼,只有你沒想到的應用場景。下面就說一下最近技術組件出現的一次離奇的故障。

      開始之前,先看看這個服務的壓力,大約每分鍾3700左右的樣子,折合成TPS也就不到100的樣子。

image

      問題現象是,當服務程序重啟后,系統一直沒有結束的任務,並且線程持續增長,直到線程數增長到最大。

  image

      通過Dump分析發現,所有的線程都被一個線程阻塞,阻塞線程18.

image

      進一步分析18號線程的堆棧發現,SetDatabaseConfig方法創建了一個Task,一直等待沒有返回。

    image

      OK,看到這里,立馬看一下程序的代碼吧。代碼中一個web請求,並且沒有超時時間。

image

      分析了這點代碼后,簡單整理了一下整個邏輯的調用過程。大體如下:

image

      說明一下,獲取數據庫連接時,有一個全局的程序鎖,鎖中的邏輯是通過HttpClient遠程訪問WebAPI獲取連接信息,並緩存到本地,上面的源代碼就是在請求配置數據。通過上圖我們可以看出,在HttpClient返回前,所有線程池中執行的任務,如果與數據庫訪問有關,都會被掛起。並且此時RPC服務持續受到請求,收到請求很快會被RPC服務消費並創建線程后交給線程池。這樣可以解釋為什么TeldHost.exe的線程持續增長的原因了。

      分析到這里,還是毫無頭緒,第一次調用加鎖了,沒有並發請求,按理說很快就可以返回了。百思不得其解,干脆寫個模擬程序測測獲取數據庫連接的方法吧。

image[29]

      通過模擬程序測得,10並發時1s左右所有調用都完成了,100並發時需要9秒多,1000並發時需要350s。尼瑪,果然有問題。通過review里面的代碼,極度懷疑是HttpClient搞的鬼。通過翻看HttpClient的源代碼,我們會發現,后台是通過Task實現的異步請求。看到這里恍然大悟,典型的HttpClient發起請求后,創建的task ,但是task拿不到線程的執行權引起的線程資源競爭。因為來自前端的壓力一直很大,一直沒有空閑線程,HttpClient一直在處理阻塞等待狀態。

      果斷把HttpClient異步調用改為同步調用,通過多次壓測驗證,問題得到解決。下面是修復后的代碼。

image


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM