TCP連接CLOSE_WAIT和TIME_WAIT狀態學習總結

0、引言

生產環境中,運行一段時間後,發現經常出現殘留着大量的TCP鏈接,如下圖所示,這些殘留的TCP鏈接佔用着系統的資源,一旦數量過大的話,會導致系統資源耗盡,甚至崩潰,急需排查解決。
CLOSE_WAIT
TIME_WAIT

2、TCP/IP的連接和斷開過程

TCP連接和斷開過程

2.1 三次握手建立連接

  1. 第一次握手:建立連接時,客戶端A發送SYN包(SYN=j)到服務器B,並進入SYN_SEND狀態,等待服務器B確認。
  2. 第二次握手:服務器B收到SYN包,必須發生一個ACK包,來確認客戶A的SYN(ACK=j+1),同時自己也發送一個SYN包(SYN=k),即SYN+ACK包,此時服務器B進入SYN_RECV狀態。
  3. 第三次握手:客戶端A收到服務器B的SYN+ACK包,向服務器B發送確認包ACK(ACK=k+1),此包發送完畢,客戶端A和服務器B進入ESTABLISHED狀態,完成三次握手(注意,主動打開方的最後一個ACK包中可能會攜帶了它要發送給服務端的數據)。

總結: 三次握手,其實就是主動打開方,發送SYN,表示要建立連接,然後被動打開方對此進行確認,表示可以,然後主動方收到確認之後,對確認進行確認;

2.2 四次揮手斷開連接

由於TCP連接是全雙工的,因此每個方向都必須單獨進行關閉,TCP的雙方都要向對方發送一次 FIN 包,並且要對方對次進行確認。根據兩次FIN包的發送和確認可以將四次揮手分爲兩個階段:

第一階段: 主要是主動閉方方發生FIN,被動方對它進行確認;

  1. 第一次揮手:主動關閉方,客戶端發送完數據之後,向服務器發送一個FIN(M)數據包,進入 FIN_WAIT1 狀態;被動關閉方服務器收到FIN(M)後,進入 CLOSE_WAIT 狀態;
  2. 第二次揮手:服務端發生FIN(M)的確認包ACK(M+1),關閉服務器讀通道,進入 LAST_ACK 狀態;客戶端收到ACK(M+1)後,關閉客戶端寫通道,進入 FIN_WATI2狀態;此時客戶端仍能通過讀通道讀取服務器的數據,服務器仍能通過寫通道寫數據。

第二階段: 主要是被動關閉方發生FIN,主動方對它進行確認;

  1. 第三次揮手:服務器發送完數據,向客戶機發送一個FIN(N)數據包,狀態沒有變還是LAST_ACK;客戶端收到FIN(N)後,進入 TIME_WAIT 狀態
  2. 第四次揮手:客戶端返回對FIN(N)的確認段ACK(N+1),關閉客戶機讀通道(還是 TIME_WAIT 狀態);服務器收到ACK(N+1)後,關閉服務器寫通道,進入CLOSED狀態。

總結: 四次揮手,其本質就是:主動關閉方數據發生完成之後 發生FIN,表示我方數據發生完,要斷開連接,被動方對此進行確認;然後被動關閉方在數據發生完成之後 發生FIN,表示我方數據發生完成,要斷開連接,主動方對此進行確認;

3、CLOSE_WAIT

3.1 CLOSE_WAIT產生的原因

由上面的TCP四次揮手斷開連接的過程,可以知道 CLOSE_WAIT 是主動關閉方發生FIN之後,被動方收到 FIN 就進入了 CLOSE_WAIT 狀態,此時如果被動方沒有調用 close() 函數來關閉TCP連接,那麼被動方服務器就會一直處於 CLOSE_WAIT 狀態(等待調用close函數的狀態);
所以 CLOSE_WAIT 狀態很多的原因有兩點:

  • 代碼中沒有寫關閉連接的代碼,也就是程序有bug;
  • 該連接的業務代碼處理時間太長,代碼還在處理,對方已經發起斷開連接請求; 也就是客戶端因爲某種原因先於服務端發出了FIN信號,導致服務端被動關閉,若服務端不主動關閉socket發FIN給Client,此時服務端Socket會處於 CLOSE_WAIT 狀態(而不是 LAST_ACK 狀態)。

3.2 CLOSE_WAIT的特性

由於某種原因導致的 CLOSE_WAIT ,會維持一段時間。如果服務端程序因某個原因導致系統造成一堆 CLOSE_WAIT 消耗資源,那麼通常是等不到釋放那一刻,系統就已崩潰。TOMCAT失去響應等等。
如下圖(平臺使用的配置):
平臺參數
tcp_keepalive_time(7200):如果在該參數指定的秒數內,TCP連接一直處於空閒,則內核開始向客戶端發起對它的探測,看他是否還存活着;
tcp_keepalive_intvl(75):以該參數指定的秒數爲時間間隔,向客戶端發起對它的探測;
tcp_keepalive_probes(9):內核發起對客戶端探測的次數,如果都沒有得到相應,那麼就斷定客戶端不可達或者已關閉,內核就關閉該TCP連接,釋放相關資源;

所以 CLOSE_WAIT 狀態維持的秒數=tcp_keepalive_time+tcp_keepalive_intvl* tcp_keepalive_probes=7200+75*9=7875秒(約130分鐘)

3.3 CLOSE_WAIT的解決方法

  • 若是系統的bug,那麼找到並修正bug;

  • 修改TCP/IP的keepalive的相關參數來縮短 CLOSE_WAIT 狀態維持的時間;
    修改方法:

    sysctl -w net.ipv4.tcp_keepalive_time=1800   
    sysctl -w net.ipv4.tcp_keepalive_probes=3
    sysctl -w net.ipv4.tcp_keepalive_intvl=15
    sysctl -p
    

    修改會暫時生效,重新啓動服務器後,會還原成默認值。
    修改之後,進行觀察一段時間,如果效果理想,那麼可以進行永久性修改:

    在文件 /etc/sysctl.conf 中的添加或者修改成下面的內容:

    net.ipv4.tcp_keepalive_time = 1800 
    net.ipv4.tcp_keepalive_probes = 3 
    net.ipv4.tcp_keepalive_intvl = 15 
    

    修改之後執行: sysctl -p 使修改馬上生效。

3.4 數據庫連接池CLOSE_WAIT問題分析

假如連接池中的連接被數據庫關閉了,應用通過連接池getConnection時可能獲取到這些不可用的連接,且這些連接如果不被其他線程回收的話,它們不會被連接池被廢除,也不會重新被創建,佔用了連接池的名額,項目本身作爲服務端,數據庫鏈接被關閉,客戶端調用服務端就會出現大量的timeout,客戶端設置了超時時間,然而主動斷開,服務端必然出現 CLOSE_WAIT 。加大tomcat默認線程(server.tomcat.max-threads)只能緩解。

Tomcat 連接池中相關配置項的作用:

  • testOnBorrow:true指明是否在從池中取出連接前進行檢驗,如果檢驗失敗,則從池中去除連接並嘗試取出另一個
  • testWhileIdle:默認false,建議設置爲true,指明連接是否被空閒連接回收器(如果有)進行檢驗,如果檢測失敗,則連接將被從池中去除。
  • timeBetweenEvictionRunsMillis = “30000” 如果設置爲非正數,則不運行空閒連接回收器線程,每30秒運行一次空閒連接回收器
  • minEvictableIdleTimeMillis = “1800000” 池中的連接空閒30分鐘後被回收,默認值就是30分鐘。
  • numTestsPerEvictionRun=“3” 在每次空閒連接回收器線程(如果有)運行時檢查的連接數量,默認值就是3。

配置 timeBetweenEvictionRunsMillis = "30000" 後,每30秒運行一次空閒連接回收器(獨立線程)。並每次檢查3個連接,如果連接空閒時間超過30分鐘就銷燬。銷燬連接後,連接數量就少了,如果小於minIdle數量,就新建連接,維護數量不少於minIdle,過行了新老更替。

配置 testWhileIdle = "true" 表示每30秒,取出3條連接,使用validationQuery = “SELECT 1” 中的SQL進行測試 ,測試不成功就銷燬連接。銷燬連接後,連接數量就少了,如果小於minIdle數量,就新建連接。

4、TIME_WAIT

4.1 TIME_WAIT產生的原因

TCP連接是雙向的,所以在關閉連接的時候,兩個方向各自都需要關閉。先發FIN包的一方執行的是主動關閉;後發FIN包的一方執行的是被動關閉。主動關閉的一方會進入 TIME_WAIT 狀態,並且在此狀態停留2倍的MSL(報文最大生存時間)時長。平臺系統使用的是默認值60s。也就是說 TIME_WAIT 狀態需要維持120秒才能釋放。

在生產過程中,如果服務器使用短連接,那麼完成一次請求後會主動斷開連接,就會造成大量 TIME_WAIT 狀態。因此我們常常在系統中會採用長連接,減少建立連接的消耗,同時也減少 TIME_WAIT 的產生,但實際上即使使用長連接配置不當時,當 TIME_WAIT 的生產速度遠大於其消耗速度時,系統仍然會累計大量的 TIME_WAIT 狀態的連接。 TIME_WAIT 狀態連接過多就會造成一些問題。如果客戶端的 TIME_WAIT 連接過多,同時它還在不斷產生,將會導致客戶端端口耗盡,新的端口分配不出來,出現錯誤,tomcat也會進入假死狀態。如果服務器端的 TIME_WAIT 連接過多,可能會導致客戶端的請求連接失敗。

4.2 TIME_WAIT相關參數調優

查看當前系統的配置
在這裏插入圖片描述
tcp_tw_reuse:是否能夠重新啓用處於 TIME_WAIT 狀態的TCP連接用於新的連接;
tcp_tw_recycle:設置是否對 TIME_WAIT 狀態的TCP進行快速回收;
tcp_fin_timeout:主動關閉方TCP保持在FIN_WAIT_2狀態的時間。對方可能會一直不結束連接或不可預料的進程死亡。默認值爲60秒。

修改方法:

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 -p

修改會暫時生效,重新啓動服務器後,會還原成默認值。修改之後,進行觀察一段時間,如果效果理想,可以進行永久性修改:修改 /etc/sysctl.conf:

net.ipv4.tcp_tw_reuse=1   
net.ipv4.tcp_tw_recycle=1
net.ipv4.tcp_fin_timeout=30

5、測試

查看各種狀態的網絡連接的數量,Linux 下使用命令:

netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'

可以查看詳情,Linux 下使用命令:

netstat -na

某測試環境參數調整前:
調整參數前
某測試環境參數調整後:
調整參數後

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