分佈式事務Lottor在測試環境中運行一段時間以後,出現Lottor客戶端鏈接不上Lottor Server的狀況。通過排查,發現根源問題是Lottor客戶端獲取不到Lottor Server的集羣信息。html
Lottor Server啓動了兩個端口:9666爲Tomcat容器的端口、9888爲netty 服務器的端口。經過以下命令查看端口的狀態:程序員
netstat -apn
複製代碼
netstat -apn|grep 9888
複製代碼
首先查看了netty服務器的端口,鏈接很正常,和咱們上面描述的問題沒什麼聯繫。排除netty服務鏈接的因素。apache
其次查看Tomcat容器的端口的狀態:tomcat
netstat -apn|grep 9666
複製代碼
發現有大量的FIN_WAIT2 和 CLOSE_WAIT狀態的鏈接。 bash
另外還能夠經過以下的命令查看當前的鏈接數:服務器
netstat -apn|grep 9666 | wc -l
複製代碼
更加細化能夠發現FIN_WAIT2 和 CLOSE_WAIT狀態的鏈接數量至關。微信
通訊的客戶端和服務端經過三次握手創建TCP鏈接。當數據傳輸完畢後,雙方均可釋放鏈接。最開始的時候,客戶端和服務器都是處於ESTABLISHED狀態,而後客戶端主動關閉,服務器被動關閉。網絡
鏈接的主動斷開是能夠發生在客戶端,也一樣能夠發生在服務端。經常使用的三個狀態是:ESTABLISHED 表示正在通訊,TIME_WAIT 表示主動關閉,CLOSE_WAIT 表示被動關閉。負載均衡
最開始的時候,客戶端和服務器都是處於ESTABLISHED狀態,而後客戶端或服務端發起主動關閉,另外一端則是被動關閉。socket
當一方接受到來自應用斷開鏈接的信號時候,就發送 FIN 數據報來進行主動斷開,而且該鏈接進入 FIN_WAIT1 狀態,鏈接處於半段開狀態(能夠接受、應答數據,當不能發送數據),並將鏈接的控制權託管給 Kernel,程序就再也不進行處理。通常狀況下,鏈接處理 FIN_WAIT1 的狀態只是持續很短的一段時間。
被動關閉的一端在收到FIN包文以後,其所處的狀態爲CLOSE_WAIT,表示被動關閉。
當主動斷開一端的 FIN 請求發送出去後,而且成功夠接受到相應的 ACK 請求後,就進入了 FIN_WAIT2 狀態。其實 FIN_WAIT1 和 FIN_WAIT2 狀態都是在等待對方的 FIN 數據報。當 TCP 一直保持這個狀態的時候,對方就有可能永遠都不斷開鏈接,致使該鏈接一直保持着。
從以上TCP鏈接關閉的狀態轉換圖能夠看出,主動關閉的一方在發送完對對方FIN報文的確認(ACK)報文後,會進入TIME_WAIT狀態。TIME_WAIT狀態也稱爲2MSL狀態。MSL值得是數據包在網絡中的最大生存時間。產生這種結果使得這個TCP鏈接在2MSL鏈接等待期間,定義這個鏈接的四元組(客戶端IP地址和端口,服務端IP地址和端口號)不能被使用。
在上一小節介紹了TCP四次揮手的相關狀態以後,咱們將會分析在什麼狀況下,鏈接處於CLOSE_WAIT狀態呢? 在被動關閉鏈接狀況下,已經接收到FIN,可是尚未發送本身的FIN的時刻,鏈接處於CLOSE_WAIT狀態。 一般來說,CLOSE_WAIT狀態的持續時間應該很短,正如SYN_RCVD狀態。可是在一些特殊狀況下,就會出現鏈接長時間處於CLOSE_WAIT狀態的狀況。
出現大量close_wait的現象,主要緣由是某種狀況下對方關閉了socket連接,可是另外一端因爲正在讀寫,沒有關閉鏈接。代碼須要判斷socket,一旦讀到0,斷開鏈接,read返回負,檢查一下errno,若是不是AGAIN,就斷開鏈接。
Linux分配給一個用戶的文件句柄是有限的,而TIME_WAIT和CLOSE_WAIT兩種狀態若是一直被保持,那麼意味着對應數目的通道就一直被佔着,一旦達到句柄數上限,新的請求就沒法被處理了,接着就是大量Too Many Open Files異常,致使tomcat崩潰。關於TIME_WAIT過多的解決方案參見TIME_WAIT數量太多。
從原理上來說,因爲Server的Socket在客戶端已經關閉時而沒有調用關閉,形成服務器端的鏈接處在「掛起」狀態,而客戶端則處在等待應答的狀態上。此問題的典型特徵是:一端處於FIN_WAIT2 ,而另外一端處於CLOSE_WAIT。具體來講:
1.代碼層面上未對鏈接進行關閉,好比關閉代碼未寫在 finally 塊關閉,若是程序中發生異常就會跳過關閉代碼,天然未發出指令關閉,鏈接一直由程序託管,內核也無權處理,天然不會發出 FIN 請求,致使鏈接一直在 CLOSE_WAIT 。
2.程序響應過慢,好比雙方進行通信,當客戶端請求服務端遲遲得不到響應,就斷開鏈接,從新發起請求,致使服務端一直忙於業務處理,沒空去關閉鏈接。這種狀況也會致使這個問題。
Lottor中,客戶端定時刷新本地存儲的Lottor Server的地址信息,具體來講是每60秒刷新一次,經過HttpClient請求獲取結果。筆者根據網上查找的資料和如上的分析,首先嚐試將週期性刷新關閉,觀察Lottor Server的9666端口的鏈接數。以前線性增加的鏈接數停了下來,初步定位到問題的根源。
服務器A是一臺爬蟲服務器,它使用簡單的HttpClient去請求資源服務器B上面的apache獲取文件資源,正常狀況下,若是請求成功,那麼在抓取完 資源後,服務器A會主動發出關閉鏈接的請求,這個時候就是主動關閉鏈接,服務器A的鏈接狀態咱們能夠看到是TIME_WAIT。若是一旦發生異常呢?假設 請求的資源服務器B上並不存在,那麼這個時候就會由服務器B發出關閉鏈接的請求,服務器A就是被動的關閉了鏈接,若是服務器A被動關閉鏈接以後程序員忘了 讓HttpClient釋放鏈接,那就會形成CLOSE_WAIT的狀態了。
筆者的場景如上面的狀況通常,該問題的解決方法是咱們在遇到異常的時候應該直接調用停止本次鏈接,以防CLOSE_WAIT狀態的持續。案例能夠參見HttpClient鏈接池拋出大量ConnectionPoolTimeoutException: Timeout waiting for connection異常排查。筆者後來的改進方法是重寫了這部分的邏輯,由Spring Cloud FeignClient調用執行,避免以前的負載均衡查詢等繁瑣的操做。