time_wait的成因和解決方案

myownstars發表於2014-11-15

建立TCP需要3次握手,而終止TCP需要4次互動;

 
主動關閉socket的一方最終為time_wait,被動關閉的則為close_wait;


為什麼time_wait需要2*MSL等待時間?
MSL就是maximum segment lifetime(最大分節生命期),這是一個IP資料包能在網際網路上生存的最長時間,超過這個時間將在網路中消失。
假設最終的 ACK 丟失 , server 將重發 FIN , client 必須維護 TCP 狀態資訊以便可以重發最終的 ACK ,否則會傳送RST ,結果 server 認為發生錯誤。 
若要TCP可靠地終止連線的兩個方向 ( 全雙工關閉 ) , client 必須進 TIME_WAIT狀態。
現在我們考慮終止連線時的被動方傳送了一個FIN,然後主動方回覆了一個ACK,然而這個ACK可能會丟失,這會造成被動方重發FIN,這個FIN可能會在網際網路上存活MSL。
如果沒有TIME_WAIT的話,假設連線1已經斷開,然而其被動方最後重發的那個FIN(或者FIN之前傳送的任何TCP分段)還在網路上,然而連線2重用了連線1的所有的5元素(源IP,目的IP,TCP,源埠,目的埠),剛剛將建立好連線,連線1遲到的FIN到達了,這個FIN將以比較低但是確實可能的概率終止掉連線2.


如何消除大量TCP短連線引發的TIME_WAIT?
1)可以改為長連線,但代價較大,長連線太多會導致伺服器效能問題,而且PHP等指令碼語言,需要通過proxy之類的軟體才能實現長連線;
2)修改ipv4.ip_local_port_range,增大可用埠範圍,但只能緩解問題,不能根本解決問題;
3)客戶端程式中設定socket的SO_LINGER選項;
4)客戶端機器開啟tcp_tw_recycle和tcp_timestamps選項;
5)客戶端機器開啟tcp_tw_reuse和tcp_timestamps選項;
6)客戶端機器設定tcp_max_tw_buckets為一個很小的值

So_linger的作用
struct linger {
     int l_onoff; /* 0 = off, nozero = on */
     int l_linger; /* linger time */
};
其取值和處理如下:
1、設定 l_onoff為0,則該選項關閉,l_linger的值被忽略,等於核心預設情況,close呼叫會立即返回給呼叫者,如果可能將會傳輸任何未傳送的資料;
2、設定 l_onoff !=0 && l_linger = 0,則套介面關閉時TCP夭折連線,TCP將丟棄保留在套介面傳送緩衝區中的任何資料併傳送一個RST給對方,而不是通常的四分組終止序列,這避免了TIME_WAIT狀態;
3、設定 l_onoff != 0 && l_linger != 0,當套介面關閉時核心將拖延一段時間(由l_linger決定)。
如果套介面緩衝區中仍殘留資料,程式將處於睡眠狀態,直到
(a)所有資料傳送完且被對方確認,之後進行正常的終止序列(描述字訪問計數為0)
 或(b)延遲時間到。
此種情況下,應用程式檢查close的返回值是非常重要的,如果在資料傳送完並被確認前時間到,close將返回EWOULDBLOCK錯誤且套介面傳送緩衝區中的任何資料都丟失。

close的成功返回僅告訴我們傳送的資料(和FIN)已由對方TCP確認,它並不能告訴我們對方應用程式是否已讀了資料。如果套介面設為非阻塞的,它將不等待close完成。


tcp_tw_recycle
tcp_tw_recycle選項作用為:Enable fast recycling TIME-WAIT sockets. Default value is 0.
tcp_timestamps選項作用為:TCP timestamps are used to provide protection against wrapped sequence numbers. 預設值為1。
1)快速回收到底有多快?
2)有的資料說只要開啟tcp_tw_recycle即可,有的又說要tcp_timestamps同時開啟,具體是哪個正確?
3)為什麼從虛擬機器NAT出去發起客戶端連線時選項無效,非虛擬機器連線就有效?
計算快速回收的時間,等於 RTO * 3.5,回答第一個問題的關鍵是RTO(Retransmission Timeout)大概是多少
RFC中有關於RTO計算的詳細規定,一共有三個:RFC-793、RFC-2988、RFC-6298,Linux的實現是參考RFC-2988。
=====linux-2.6.37 net/ipv4/tcp.c 126================
#define TCP_RTO_MAX ((unsigned)(120*HZ))
#define TCP_RTO_MIN ((unsigned)(HZ/5))
==========================================
這裡的HZ是1s,因此可以得出RTO最大是120s,最小是200ms,對於區域網的機器來說,正常情況下RTO基本上就是200ms,因此3.5 RTO就是700ms
1)快速回收到底有多快?
區域網環境下,700ms就回收;
2)有的資料說只要開啟tcp_tw_recycle即可,有的又說要tcp_timestamps同時開啟,具體是哪個正確?
需要同時開啟,但預設情況下tcp_timestamps就是開啟的,所以會有人說只要開啟tcp_tw_recycle即可;
3)為什麼從虛擬機器發起客戶端連線時選項無效,非虛擬機器連線就有效?
和網路組網有關係,無法獲取對端資訊時就不進行快速回收;


tcp_tw_reuse
tcp_tw_reuse選項的含義如下
tcp_tw_reuse - BOOLEAN
Allow to reuse TIME-WAIT sockets for new connections when it is safe from protocol viewpoint. Default value is 0.
這裡的關鍵在於“協議什麼情況下認為是安全的”,由於環境限制,沒有辦法進行驗證,通過看原始碼簡單分析了一下。
=====linux-2.6.37 net/ipv4/tcp_ipv4.c 114=====
int tcp_twsk_unique(struct sock *sk, struct sock *sktw, void *twp)
總結一下:
1)tcp_tw_reuse選項和tcp_timestamps選項也必須同時開啟;
2)重用TIME_WAIT的條件是收到最後一個包後超過1s。
官方手冊有一段警告:
It should not be changed without advice/request of technical experts.
對於大部分區域網或者公司內網應用來說,滿足條件2)都是沒有問題的,因此官方手冊裡面的警告其實也沒那麼可怕:)


tcp_max_tw_buckets
參考官方文件(http://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt),解釋如下:
tcp_max_tw_buckets - INTEGER
Maximal number of timewait sockets held by system simultaneously. If this number is exceeded time-wait socket is immediately destroyed and warning is printed. 
官方文件沒有說明預設值,通過幾個系統的簡單驗證,初步確定預設值是180000。
官方手冊中有一段警告:
    This limit exists only to prevent simple DoS attacks, you _must_ not lower the limit artificially, but rather increase it (probably, after increasing installed memory),if network conditions require more than default value.
基本意思是這個用於防止Dos攻擊,我們不應該人工減少,如果網路條件需要的話,反而應該增加。


參考資料

http://coolshell.cn/articles/1484.html

http://www.iteblog.com/archives/169

http://blog.csdn.net/yunhua_lee/article/details/8146830




來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/15480802/viewspace-1334381/,如需轉載,請註明出處,否則將追究法律責任。

相關文章