1. 程式人生 > >Thrift java.net.SocketException: Broken pipe問題分析定位

Thrift java.net.SocketException: Broken pipe問題分析定位

在實際的thrift使用過程中,thrift客戶端跟服務端通訊會有時爆出org.apache.thrift.transport.TTransportException: java.net.SocketException: Broken pipe異常,復現概率不高,很是困惑不知道是由什麼原因引起的,在此記錄一下分析和定位的過程,以備檢視。

環境:
-centos 7 
-jdk1.7 
-thrift 0.9.1 
-連線管理使用的物件池

問題產生的原因
這個異常產生的原因為thrift客戶端向一個已經關閉的socket,寫入資料導致該異常發生,在我們的客戶端表現為client從物件池獲取一個TCP長連線,然後在這個長連線上給服務端傳送校驗,校驗連線是否存活,在實際寫入探測字串的時候,該異常發生,由TIOStreamTransport類在flush的時候丟擲,關健的問題點在於客戶端向一個已經關閉的socket做寫入操作。 
這裡讓我們推測一下,首先該連線已經在伺服器端被伺服器端顯示關閉了,而客戶端無法感知該連線已經被關閉,為什麼伺服器端會將這個連線關閉掉呢?原因有很多,例如:

伺服器keeplive心跳,檢測發現客戶端存活,但是網路不可達,伺服器就會關閉這個連線
連線由於其他原因,例如socket傳輸異常,導致伺服器將這個連線關閉
我們初步懷疑是第一種原因導致的,因為在這個問題發生時,我們的客戶端所在的服務還在正常執行。 
下面我來看問題發生時的現象。 
客戶端截圖: 

根據圖中,我們明顯看到客戶端處於CLOSE-WAIT狀態,而根據TCP狀態圖,我們知道進入這個是個狀態是由於被動關閉導致的,也就是說我們對應的thrift伺服器端將這個socket連線關閉了,而伺服器端會進入FIN-WAIT2/TIME-WAIT和CLOSED,這裡我們把TCP互動圖列舉出來: 

服務端TCP狀況截圖: 

通過截圖,我們可以很清楚的看到服務端已經將這個連線關閉了,這也就印證了我們的猜測,是由伺服器端主動關閉了連線,導致了該問題發生

thrift探究
既然這個問題是由於thrift上報上來的,我們就去探尋一下thrift客戶端的機制,看它是否能感知。通過原始碼分析得知(原始碼很多,就不具體列舉了)thrift客戶端是被動呼叫,由使用者在需要的時候顯示呼叫,進行RPC通訊,寫入資料,然後讀取返回值,這樣就導致,即使服務端給客戶端傳送了fin揮手資訊,他也不能處理,這樣也就無法有效將這個連線關閉掉,而是由物件池在實際使用中進行檢測,關閉這個失效的連線,物件池也只是管理了這個socket控制代碼,並沒有對FIN的處理。
在thrift伺服器端,我們分析查看了原始碼,發現,伺服器端也沒有針對長連線長時間不適用的應用程式的超時控制
結論
經過上文排查,基本懷疑是socket異常導致核心主動關閉連線或者心跳檢測異常關閉連線,後續會針對這兩種情況進行試驗驗證