1. 程式人生 > >客戶端產生CLOSE_WAIT狀態的解決方案

客戶端產生CLOSE_WAIT狀態的解決方案

現象

生產環境和測試環境都發現有個外圍應用通過搜尋服務調用搜索引擎時,偶爾會出現大量的訪問超時的問題,通過如下方式進行分析排查:

l 首先是拿到搜尋服務的JavaCore,發現其堵在HttpClient的傳送上面,被堵的連線有數百個,原因是不能夠從連線池中獲取到連線;

l 首先想到的就是連線池沒有釋放,檢查程式碼,也確實存在著一些呼叫沒有釋放連線,特別是在異常的情況下,針對這一部分程式碼進行修復後,可是一段時間之後還是出現了訪問超時的問題;

l 考慮到這個外圍應用的訪問現出問題的時候,其它的外圍應用調用搜索服務是沒有問題的,因此確定當前搜尋服務還沒有掛;

l 不同的外圍應用可能呼叫的後臺搜尋引擎是不一樣的,難道是該外圍應用對應的搜尋引擎出現了問題?不過經過對該搜尋引擎進行分析,該搜尋引擎本身是正常的,但是有一個奇怪的現象,在外圍應用調用搜索服務發生超時時,搜尋引擎本身沒有接受到任何請求;

l 也就是說經搜尋服務的請求都沒有提交到該搜尋引擎上,難道是搜尋服務與該搜尋引擎之間的連線有問題?通過網路排查,使用PingTelnet進行正向和反向確定,從搜尋服務和搜尋引擎之間的網路是正常的,且埠也可以正常訪問;

l 再回到搜尋服務所在的伺服器,通過netstat一看,發現有400CLOSE_WAIT與該搜尋引擎相關的連線,這個數字恰好是應用中設定的單個Route所能夠連線的最大連線數。

分析到此,問題就明朗了,HttpClient連線池的中建立連線數已經達到了最大數字,不能夠建立新的連線了,已經建立的連線都是在等待關閉(CLOSE_WAIT)的狀態,沒有被放回到可用的連線池中,不能夠用於處理新的連線請求,因而所有的請求都是堵在了從連線池中獲取連線哪裡。

要解決這個問題,首先需要知道CLOSE_WAIT產生的原因,才能夠解決該問題,或者減少該問題的發生。

TCP連線關閉時需要四次握手才能夠完成,如下圖所示:

 

產生CLOSE_WAIT狀態的一方,是屬於被動關閉的一方,用簡單的話對解釋上圖(主動關閉方為A,被動關閉方為B):

A發一條FIN(關閉)請求給B,說我要關閉了;

B迴應一條ACK(確認)請求給A,說我知道了,你關吧,此時B就會進行CLOSE_WAIT狀態;

B傳送一條FIN(關閉)請給A,說我要關閉了;

A收到傳送一條ACK(確認)訊息說,你關閉吧。

上面四次握手完成後,雙方的連線就都關閉了,但是這裡在客戶端產生了CLOSE_WAIT現象,首先可以確定的是服務端主動關閉的連線,且客戶端沒有給服務端傳送關閉的請求(第三次握手請求),就會一直處在

CLOSE_WAIT的狀態,可是客戶端為什麼不向服務端傳送關閉的請求,它當時在忙什麼呢,難道應用在關閉前有哪麼多事情要做?還有就是為什麼服務會主動關掉客戶端的這麼多連線?

有人說這可能是服務端在呼叫關閉時,而客戶端正在執行RECV(資料接收),這時候有可能服務端傳送的FIN包客戶端接收出錯,就是由TCP代回了一個ACK包,所以客戶端就會處在CLOSE_WAIT的狀態中,因而建議判斷RECV時是否出錯,如果出錯就主動關閉連線,這樣就可以防止沒有接收到FIN包。

也有人說這是由於客戶端請求服務端時,超時就有可能出現這種情況,我對這種情況做了實驗,分別啟動了客戶端和服務端,在服務端中暴露一個超時的服務介面,在客戶端中通過POST的方式呼叫,然後再通過第三方工具呼叫客戶端去呼叫服務端的超時介面,測試分別在Linux以及Windows平臺進行了測試,可是經過100萬個連線超時的請求後,客戶端沒有出現CLOSE-WAIT的現象,只有服務端才出現了CLOSE-WAIT,並且都會正常的關閉。

我們嘗試過優化LinuxTCP連線引數,減少TCP的連線時間以及增加連線的可用性,如下:

sysctl -w net.ipv4.tcp_timestamps=0 

sysctl -w net.ipv4.tcp_tw_reuse=1 

sysctl -w net.ipv4.tcp_tw_recycle=1 

sysctl -w net.ipv4.tcp_fin_timeout=30 

sysctl -w net.ipv4.tcp_keepalive_time=1800 

sysctl -w net.ipv4.tcp_rmem="4096 87380 8388608" 

sysctl -w net.ipv4.tcp_wmem="4096 87380 8388608" 

sysctl -w net.ipv4.tcp_max_syn_backlog=4096 

也優化了HttpClient的引數,可是客戶端還是會出現CLOSE_WAIT的情況,且搜尋引擎是使用惠普的Autonomy,閉源的不好入手優化,最後還是通過在客戶端實現定時任務定期檢查當前連線中狀態為leased(拿走但沒有返回aviable可用佇列中的連線)的連線的數量,檢測到該這種連線的數量超過一定數量後,就關閉該連線池,釋放所有連線,然後重新初使化該連線池,就可以解決這種問題了,經過測試這種試是可行的。不過曾經考慮到這種方式比較暴力,連可用的連線都給關閉了,本想只關閉那些長久未釋放的連線,不過由於連線池沒有暴露操作方法,通過反射可以獲取到池中的連線,不過由於關聯資源較多,操作麻煩,最後沒有采用這種方式。