現象
首先有一個被服務由於記憶體有限,導致巨卡。導致呼叫他的服務出現執行緒阻塞。jstack列印執行緒池如下所示:
開始排查解決問題
第一步:檢查程式碼看是否超時設定是否正確,因為感覺超時設定正確不可能阻塞。
找到注入client的位置:
發現配置沒有任何問題,此時感到了一點點慌張。(內心OS: 難不成HttpClient還有BUG, 講道理這麼成熟的框架不應該啊)
第二步:本著高效的原則,百度一下是否有其他人踩過這樣的坑了。
咦,這不和遇到的一樣嗎? 心想搞定。看我jstack棧阻塞堆疊資訊,也是有重試的資訊,如下圖所示:
那麼直接更改我們的client注入程式碼為如下:
以為到這裡故事就結束了,開開心心重新部署一下,就去玩其他的了。
噩夢再次上演:幾天後,被呼叫服務再次出現卡頓,然後呼叫方又阻塞了,what fuck !
老規矩看堆疊資訊:
還是熟悉的配方,一樣的錯誤,阻塞在同一個地方。本著高效的原則,GPT了一下,然並沒有什麼用,都是一些太泛的思路,這玩意幹精細活還是有缺陷:
那這樣的話,就苦逼了,只能慢慢的擼他原始碼了。因為伺服器卡頓是偶現,還沒法除錯。找到構建過程如下:
顯然實際執行在InternalHttpClient裡面。點進去看一下他的執行過程:
發現我們配置的requestConfig可能並未生效,他可能直接從Request裡面取,那這顯然有可能在呼叫方給Request手動配置requestConfig。 找到呼叫處的程式碼:
顯然確實有可能是這個原因,那有了上次的經驗,我們要驗證一下。由於伺服器卡頓是偶現,我們debug看一下是不是走到這裡了:
發現都是-1, 那應該就是這個問題了,Request配置,重新打包部署,問題再也沒有復現過。over!
題外話
原始碼定位問題的過程省略了很多,所以看起來解決問題過程似乎很簡單。因為框架程式碼畢竟那麼多,我第一步先是猜想要先定位到他超時判斷邏輯的程式碼在哪裡,才能知道為什麼不生效。定位了很久才發現框架本身並無相關邏輯,他是構建了一個Socket去請求,在socket中有兩個時間,一個是soTimeOut(讀流超時時間), 一個是connectTimeOut(連線超時時間)。 其在建立socket的時候需要指定connectTimeOut,然後soTimeOut可在發起請求前設定。 當然這也反應出分析問題不夠冷靜, 忽略了基本的網路常識,這些成熟的框架,一開始就應該思考是不是配置不正確的問題,究其原因也是對框架不夠深入瞭解,不知道具體的某個請求可能還存在可以配置單獨的RequestConfig物件,一味的關注CloseableHttpClient的配置去了。