淺談CLOSE_WAIT

淺談CLOSE_WAIT

TCP 有很多連接狀態,每一個都夠聊十塊錢兒的,比如我們以前討論過 TIME_WAIT 和 FIN_WAIT1,最近時不時聽人提起 CLOSE_WAIT,感覺有必要梳理一下。

 

所謂 CLOSE_WAIT,借用某位大牛的話來說應該倒過來叫做 WAIT_CLOSE,也就是說「等待關閉」,如果你還不理解其含義,可以看看 TCP 關閉連接時的圖例:

TCP Close

TCP Close

不要被圖中的 client 和 server 所迷惑,你只要記住:主動關閉的一方發出 FIN 包,被動關閉的一方響應 ACK 包,此時,被動關閉的一方就進入了 CLOSE_WAIT 狀態。如果一切正常,稍後被動關閉的一方也會發出 FIN 包,然後遷移到 LAST_ACK 狀態。

通常,CLOSE_WAIT 狀態在服務器停留時間很短,如果你發現大量的 CLOSE_WAIT 狀態,那麼就意味着被動關閉的一方沒有及時發出 FIN 包,一般有如下幾種可能:

  • 程序問題:如果代碼層面忘記了 close 相應的 socket 連接,那麼自然不會發出 FIN 包,從而導致 CLOSE_WAIT 累積;或者代碼不嚴謹,出現死循環之類的問題,導致即便後面寫了 close 也永遠執行不到。
  • 響應太慢或者超時設置過小:如果連接雙方不和諧,一方不耐煩直接 timeout,另一方卻還在忙於耗時邏輯,就會導致 close 被延後。響應太慢是首要問題,不過換個角度看,也可能是 timeout 設置過小。
  • BACKLOG 太大:此處的 backlog 不是 syn backlog,而是 accept 的 backlog,如果 backlog 太大的話,設想突然遭遇大訪問量的話,即便響應速度不慢,也可能出現來不及消費的情況,導致多餘的請求還在隊列裏就被對方關閉了。

如果你通過「netstat -ant」或者「ss -ant」命令發現了很多 CLOSE_WAIT 連接,請注意結果中的「Recv-Q」和「Local Address」字段,通常「Recv-Q」會不爲空,它表示應用還沒來得及接收數據,而「Local Address」表示哪個地址和端口有問題,我們可以通過「lsof -i:<PORT>」來確認端口對應運行的是什麼程序以及它的進程號是多少。

如果是我們自己寫的一些程序,比如用 HttpClient 自定義的蜘蛛,那麼八九不離十是程序問題,如果是一些使用廣泛的程序,比如 Tomcat 之類的,那麼更可能是響應速度太慢或者 timeout 設置太小或者 BACKLOG 設置過大導致的故障。

此外還有一點需要說明:按照前面圖例所示,當被動關閉的一方處於 CLOSE_WAIT 狀態時,主動關閉的一方處於 FIN_WAIT2 狀態。 那麼爲什麼我們總聽說 CLOSE_WAIT 狀態過多的故障,但是卻相對少聽說 FIN_WAIT2 狀態過多的故障呢?這是因爲 Linux 有一個「tcp_fin_timeout」設置,控制了 FIN_WAIT2 的最大生命週期。壞消息是 CLOSE_WAIT 沒有類似的設置,如果不重啓進程,那麼 CLOSE_WAIT 狀態很可能會永遠持續下去;好消息是如果 socket 開啓了 keepalive 機制,那麼可以通過相應的設置來清理無效連接,不過 keepalive 是治標不治本的方法,還是應該找到問題的癥結纔對。

發佈了29 篇原創文章 · 獲贊 97 · 訪問量 49萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章