關於大量CLOSE_WAIT連接分析

問題場景

某日線上登錄出現故障,排查日誌發現HttpClient請求時隨機分配到的端口被佔用,導致第三方登錄拉取信息時無法拉取成功,錯誤如下:

java.net.BindException: Address already in use (Bind failed)
	at java.net.PlainSocketImpl.socketBind(Native Method) ~[na:1.8.0_111]
	at java.net.AbstractPlainSocketImpl.bind(AbstractPlainSocketImpl.java:387) ~[na:1.8.0_111]
	at java.net.Socket.bind(Socket.java:644) ~[na:1.8.0_111]
	at sun.reflect.GeneratedMethodAccessor2044.invoke(Unknown Source) ~[na:na]
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_111]
	at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_111]
	at org.apache.commons.httpclient.protocol.ReflectionSocketFactory.createSocket(ReflectionSocketFactory.java:139) ~[commons-httpclient-3.1.jar:na]
	at org.apache.commons.httpclient.protocol.DefaultProtocolSocketFactory.createSocket(DefaultProtocolSocketFactory.java:125) ~[commons-httpclient-3.1.jar:na]
	at org.apache.commons.httpclient.HttpConnection.open(HttpConnection.java:707) ~[commons-httpclient-3.1.jar:na]
.....

這個問題很奇怪,linux端口分配會避免端口衝突的,然後檢查服務器發現大量tcp連接處於CLOSE_WAIT狀態,不過對應的是另外一個項目.

統計信息如下(命令netstat -nat | awk 'FNR>2{print $NF}' | sort | uniq -c),簡直恐怖.

CLOSE_WAIT

TCP關閉連接時四次揮手的過程,如下圖所示(圖來自網絡):

有圖可知,主動方發起關閉請求也就是FIN包後,被動方接收到包,被動方接着進入CLOSE_WAIT狀態,接着被動方發送FIN包告知主動方自己已關閉後進入LAST_ACK狀態. 那麼當被動方這個FIN包沒有發送成功,那麼其就一直處於CLOSE_WAIT狀態.那麼問題成功轉換爲以下幾個小問題:

  • 大量CLOSE_WAIT有什麼危害? CLOSE_WAIT狀態不會自己消失,除非對應的應用進程死掉,不會消失就意味着一直佔用服務器資源,端口總數又只有65535,因此這裏的服務器作爲連接的發起者就會造成大量端口被佔用,一旦佔用完就導致後面的請求都發不出去,也就是一開始圖上另一個項目發請求出現的Address already in use (Bind failed)錯誤.
  • 被動方什麼情況下FIN包會發送失敗?
    • 程序問題:如果代碼層面忘記了 close 相應的 socket 連接,那麼自然不會發出 FIN 包,從而導致 CLOSE_WAIT 累積;或者代碼不嚴謹,出現死循環之類的問題,導致即便後面寫了 close 也永遠執行不到。
    • 響應太慢或者超時設置過小:如果連接雙方不和諧,一方不耐煩直接 timeout,另一方卻還在忙於耗時邏輯,就會導致 close 被延後。響應太慢是首要問題,不過換個角度看,也可能是 timeout 設置過小。
    • BACKLOG 太大:此處的 backlog 不是 syn backlog,而是 accept 的 backlog,如果 backlog 太大的話,設想突然遭遇大訪問量的話,即便響應速度不慢,也可能出現來不及消費的情況,導致多餘的請求還在隊列裏就被對方關閉了。

解決問題

知道了產生的原因,自然好解決,根據netstat給出的信息包括pid定位到具體的應用,然後通過git查看最近代碼改動,最終找到之前上線的一段代碼使用了python的httplib,使用完卻沒有主動close釋放連接,因此出現了這個問題.

那麼爲什麼HttpClient訪問時端口會分配到CLOSE_WAIT對應的端口? Linux會爲每一次請求分配臨時端口,這個分配範圍在/proc/sys/net/ipv4/ip_local_port_range中有記錄,在我這臺服務器上其值是20000-65535,大量的CLOSE_WAIT就會導致可分配的端口數減少,因此係統會在指定範圍內選擇一個沒有衝突的端口,一旦端口消耗完畢就會造成衝突.也就是上面的錯誤Address already in use (Bind failed).

TIME_WAIT

上面結果圖中TIME_WAIT也有幾百個,這個是什麼原因? 對於四次揮手過程中,當主動方接收到被動放的關閉確認信號FIN後,主動方會回覆一個ACK信號,然後會進入TIME_WAIT狀態,此時會等待2MLS,在Linux中也就是60s,因此相對上述2000多個活躍tcp來說,這100多的TIME_WAIT是正常現象. 然後爲什麼TCP主動方關閉後需要等待2MLS? 因爲TCP是可靠的通信,在主動方回覆ACK時如果由於網絡問題該包發送失敗,那麼被動方就會進行FIN重傳,此時重傳會遇到兩個場景:

  • 主動方已關閉,舊的TCP連接已經消失,那麼系統只能回覆RST包.
  • 主動方已關閉,然後利用此端口建立了新的連接.也就是舊的TCP關閉,新的TCP已建立,那麼就會造成信道的不可靠.

因此超時等待機制是必要的,

參考

淺談CLOSE_WAIT

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章