1. 程式人生 > >Time Wait的作用、原因、影響和如何避免

Time Wait的作用、原因、影響和如何避免

TIME_WAIT示例圖:

1、 time_wait的作用:

TIME_WAIT狀態存在的理由:
1)可靠地實現TCP全雙工連線的終止
   在進行關閉連線四次揮手協議時,最後的ACK是由主動關閉端發出的,如果這個最終的ACK丟失,伺服器將重發最終的FIN,
因此客戶端必須維護狀態資訊允許它重發最終的ACK。如果不維持這個狀態資訊,那麼客戶端將響應RST分節,伺服器將此分節解釋成一個錯誤(在java中會丟擲connection reset的SocketException)。
因而,要實現TCP全雙工連線的正常終止,必須處理終止序列四個分節中任何一個分節的丟失情況,主動關閉的客戶端必須維持狀態資訊進入TIME_WAIT狀態。

2)允許老的重複分節在網路中消逝
TCP分節可能由於路由器異常而“迷途”,在迷途期間,TCP傳送端可能因確認超時而重發這個分節,迷途的分節在路由器修復後也會被送到最終目的地,這個原來的迷途分節就稱為lost duplicate。
在關閉一個TCP連線後,馬上又重新建立起一個相同的IP地址和埠之間的TCP連線,後一個連線被稱為前一個連線的化身(incarnation),那麼有可能出現這種情況,前一個連線的迷途重複分組在前一個連線終止後出現,從而被誤解成從屬於新的化身。
為了避免這個情況,TCP不允許處於TIME_WAIT狀態的連線啟動一個新的化身,因為TIME_WAIT狀態持續2MSL,就可以保證當成功建立一個TCP連線的時候,來自連線先前化身的重複分組已經在網路中消逝。

2. time_wait狀態如何產生?
由上面的變遷圖,首先呼叫close()發起主動關閉的一方,在傳送最後一個ACK之後會進入time_wait的狀態,也就說該傳送方會保持2MSL時間之後才會回到初始狀態。MSL值得是資料包在網路中的最大生存時間。產生這種結果使得這個TCP連線在2MSL連線等待期間,定義這個連線的四元組(客戶端IP地址和埠,服務端IP地址和埠號)不能被使用。

3.time_wait狀態產生的原因

1)為實現TCP全雙工連線的可靠釋放

由TCP狀態變遷圖可知,假設發起主動關閉的一方(client)最後傳送的ACK在網路中丟失,由於TCP協議的重傳機制,執行被動關閉的一方(server)將會重發其FIN,在該FIN到達client之前,client必須維護這條連線狀態,也就說這條TCP連線所對應的資源(client方的local_ip,local_port)不能被立即釋放或重新分配,直到另一方重發的FIN達到之後,client重發ACK後,經過2MSL時間週期沒有再收到另一方的FIN之後,該TCP連線才能恢復初始的CLOSED狀態。如果主動關閉一方不維護這樣一個TIME_WAIT狀態,那麼當被動關閉一方重發的FIN到達時,主動關閉一方的TCP傳輸層會用RST包響應對方,這會被對方認為是有錯誤發生,然而這事實上只是正常的關閉連線過程,並非異常。

2)為使舊的資料包在網路因過期而消失

為說明這個問題,我們先假設TCP協議中不存在TIME_WAIT狀態的限制,再假設當前有一條TCP連線:(local_ip, local_port, remote_ip,remote_port),因某些原因,我們先關閉,接著很快以相同的四元組建立一條新連線。本文前面介紹過,TCP連線由四元組唯一標識,因此,在我們假設的情況中,TCP協議棧是無法區分前後兩條TCP連線的不同的,在它看來,這根本就是同一條連線,中間先釋放再建立的過程對其來說是“感知”不到的。這樣就可能發生這樣的情況:前一條TCP連線由local peer傳送的資料到達remote peer後,會被該remot peer的TCP傳輸層當做當前TCP連線的正常資料接收並向上傳遞至應用層(而事實上,在我們假設的場景下,這些舊資料到達remote peer前,舊連線已斷開且一條由相同四元組構成的新TCP連線已建立,因此,這些舊資料是不應該被向上傳遞至應用層的),從而引起資料錯亂進而導致各種無法預知的詭異現象。作為一種可靠的傳輸協議,TCP必須在協議層面考慮並避免這種情況的發生,這正是TIME_WAIT狀態存在的第2個原因。

4、大量TIME_WAIT造成的影響:

      在高併發短連線的TCP伺服器上,當伺服器處理完請求後立刻主動正常關閉連線。這個場景下會出現大量socket處於TIME_WAIT狀態。如果客戶端的併發量持續很高,此時部分客戶端就會顯示連線不上。
我來解釋下這個場景。主動正常關閉TCP連線,都會出現TIMEWAIT。

為什麼我們要關注這個高併發短連線呢?有兩個方面需要注意:
1. 高併發可以讓伺服器在短時間範圍內同時佔用大量埠,而埠有個0~65535的範圍,並不是很多,刨除系統和其他服務要用的,剩下的就更少了。
2. 在這個場景中,短連線表示“業務處理+傳輸資料的時間 遠遠小於 TIMEWAIT超時的時間”的連線

      這裡有個相對長短的概念,比如取一個web頁面,1秒鐘的http短連線處理完業務,在關閉連線之後,這個業務用過的埠會停留在TIMEWAIT狀態幾分鐘,而這幾分鐘,其他HTTP請求來臨的時候是無法佔用此埠的(佔著茅坑不拉翔)。單用這個業務計算伺服器的利用率會發現,伺服器幹正經事的時間和埠(資源)被掛著無法被使用的時間的比例是 1:幾百,伺服器資源嚴重浪費。(說個題外話,從這個意義出發來考慮伺服器效能調優的話,長連線業務的服務就不需要考慮TIMEWAIT狀態。同時,假如你對伺服器業務場景非常熟悉,你會發現,在實際業務場景中,一般長連線對應的業務的併發量並不會很高
     綜合這兩個方面,持續的到達一定量的高併發短連線,會使伺服器因埠資源不足而拒絕為一部分客戶服務。同時,這些埠都是伺服器臨時分配,無法用SO_REUSEADDR選項解決這個問題。

5、如何儘量處理TIMEWAIT過多?

編輯核心檔案/etc/sysctl.conf,加入以下內容:

net.ipv4.tcp_syncookies = 1 表示開啟SYN Cookies。當出現SYN等待佇列溢位時,啟用cookies來處理,可防範少量SYN攻擊,預設為0,表示關閉;
net.ipv4.tcp_tw_reuse = 1 表示開啟重用。允許將TIME-WAIT sockets重新用於新的TCP連線,預設為0,表示關閉;
net.ipv4.tcp_tw_recycle = 1 表示開啟TCP連線中TIME-WAIT sockets的快速回收,預設為0,表示關閉。
net.ipv4.tcp_fin_timeout 修改系預設的 TIMEOUT 時間

然後執行 /sbin/sysctl -p 讓引數生效.

/etc/sysctl.conf是一個允許改變正在執行中的Linux系統的介面,它包含一些TCP/IP堆疊和虛擬記憶體系統的高階選項,修改核心引數永久生效。

簡單來說,就是開啟系統的TIMEWAIT重用和快速回收。

如果以上配置調優後效能還不理想,可繼續修改一下配置:

vi /etc/sysctl.conf
net.ipv4.tcp_keepalive_time = 1200 
#表示當keepalive起用的時候,TCP傳送keepalive訊息的頻度。預設是2小時,改為20分鐘。
net.ipv4.ip_local_port_range = 1024 65000 
#表示用於向外連線的埠範圍。預設情況下很小:32768到61000,改為1024到65000。
net.ipv4.tcp_max_syn_backlog = 8192 
#表示SYN佇列的長度,預設為1024,加大佇列長度為8192,可以容納更多等待連線的網路連線數。
net.ipv4.tcp_max_tw_buckets = 5000 
#表示系統同時保持TIME_WAIT套接字的最大數量,如果超過這個數字,TIME_WAIT套接字將立刻被清除並列印警告資訊。
預設為180000,改為5000。對於Apache、Nginx等伺服器,上幾行的引數可以很好地減少TIME_WAIT套接字數量,但是對於 Squid,效果卻不大。此項引數可以控制TIME_WAIT套接字的最大數量,避免Squid伺服器被大量的TIME_WAIT套接字拖死。