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造成的影響:
在高並發短連接的TCP服務器上,當服務器處理完請求后立刻主動正常關閉連接。這個場景下會出現大量socket處於TIME_WAIT狀態。如果客戶端的並發量持續很高,此時部分客戶端就會顯示連接不上。
我來解釋下這個場景。主動正常關閉TCP連接,都會出現TIMEWAIT。
為什么我們要關注這個高並發短連接呢?有兩個方面需要注意:
1. 高並發可以讓服務器在短時間范圍內同時占用大量端口,而端口有個0~65535的范圍,並不是很多,刨除系統和其他服務要用的,剩下的就更少了。
2. 在這個場景中,短連接表示“業務處理+傳輸數據的時間 遠遠小於 TIMEWAIT超時的時間”的連接。
這里有個相對長短的概念,比如取一個web頁面,1秒鍾的http短連接處理完業務,在關閉連接之后,這個業務用過的端口會停留在TIMEWAIT狀態幾分鍾,而這幾分鍾,其他HTTP請求來臨的時候是無法占用此端口的(占着茅坑不拉翔)。單用這個業務計算服務器的利用率會發現,服務器干正經事的時間和端口(資源)被掛着無法被使用的時間的比例是 1:幾百,服務器資源嚴重浪費。(說個題外話,從這個意義出發來考慮服務器性能調優的話,長連接業務的服務就不需要考慮TIMEWAIT狀態。同時,假如你對服務器業務場景非常熟悉,你會發現,在實際業務場景中,一般長連接對應的業務的並發量並不會很高。
綜合這兩個方面,持續的到達一定量的高並發短連接,會使服務器因端口資源不足而拒絕為一部分客戶服務。同時,這些端口都是服務器臨時分配,無法用SO_REUSEADDR選項解決這個問題。
關於time_wait的反思:
存在即是合理的,既然TCP協議能盛行四十多年,就證明他的設計合理性。所以我們盡可能的使用其原本功能。依靠TIME_WAIT狀態來保證我的服務器程序健壯,服務功能正常。那是不是就不要性能了呢?並不是。如果服務器上跑的短連接業務量到了我真的必須處理這個TIMEWAIT狀態過多的問題的時候,我的原則是盡量處理,而不是跟TIMEWAIT干上,非先除之而后快。如果盡量處理了,還是解決不了問題,仍然拒絕服務部分請求,那我會采取負載均衡來抗這些高並發的短請求。持續十萬並發的短連接請求,兩台機器,每台5萬個,應該夠用了吧。一般的業務量以及國內大部分網站其實並不需要關注這個問題,一句話,達不到時才需要關注這個問題的訪問量。
小知識點:
TCP協議發表:1974年12月,卡恩、瑟夫的第一份TCP協議詳細說明正式發表。當時美國國防部與三個科學家小組簽定了完成TCP/IP的協議,結果由瑟夫領銜的小組捷足先登,首先制定出了通過詳細定義的TCP/IP協議標准。當時作了一個試驗,將信息包通過點對點的衛星網絡,再通過陸地電纜,再通過衛星網絡,再由地面傳輸,貫串歐洲和美國,經過各種電腦系統,全程9.4萬公里竟然沒有丟失一個數據位,遠距離的可靠數據傳輸證明了TCP/IP協議的成功。
3、案列分析:
首先,根據一個查詢TCP連接數,來說明這個問題。
netstat -ant|awk '/^tcp/ {++S[$NF]} END {for(a in S) print (a,S[a])}'
LAST_ACK 14
SYN_RECV 348
ESTABLISHED 70
FIN_WAIT1 229
FIN_WAIT2 30
CLOSING 33
TIME_WAIT 18122
狀態描述:
CLOSED:無連接是活動的或正在進行
LISTEN:服務器在等待進入呼叫
SYN_RECV:一個連接請求已經到達,等待確認
SYN_SENT:應用已經開始,打開一個連接
ESTABLISHED:正常數據傳輸狀態
FIN_WAIT1:應用說它已經完成
FIN_WAIT2:另一邊已同意釋放
ITMED_WAIT:等待所有分組死掉
CLOSING:兩邊同時嘗試關閉
TIME_WAIT:另一邊已初始化一個釋放
LAST_ACK:等待所有分組死掉
命令解釋:
先來看看netstat:
netstat -n
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 123.123.123.123:80 234.234.234.234:12345 TIME_WAIT
你實際執行這條命令的時候,可能會得到成千上萬條類似上面的記錄,不過我們就拿其中的一條就足夠了。
再來看看awk:
/^tcp/
濾出tcp開頭的記錄,屏蔽udp, socket等無關記錄。
state[]相當於定義了一個名叫state的數組
NF
表示記錄的字段數,如上所示的記錄,NF等於6
$NF
表示某個字段的值,如上所示的記錄,$NF也就是$6,表示第6個字段的值,也就是TIME_WAIT
state[$NF]表示數組元素的值,如上所示的記錄,就是state[TIME_WAIT]狀態的連接數
++state[$NF]表示把某個數加一,如上所示的記錄,就是把state[TIME_WAIT]狀態的連接數加一
END
表示在最后階段要執行的命令
for(key in state)
遍歷數組
如何盡量處理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套接字拖死。
==原理及解決辦法=====
執行主動關閉的那端經歷了這個狀態,並停留MSL(最長分節生命期)的2倍,即2MSL。
TIME_WAIT存在的兩個理由:
1 可靠的實現TCP全雙工連接的終止
2 允許老的重復的分節在網絡上的消逝
第一個:如果客戶端不維持TIME_WAIT狀態,那么將響應給服務端一個RST,該分節被服務器解釋成一個錯誤。如果TCP打算執行所有必要的工作以徹底終止某個連接上兩個方向的數據流,那么必須正確的處理。執行主動關閉的那一端是處於TIME_WAIT狀態的那一端。
第二個:端到端的連接關閉后,過一段時間相同的ip和端口間進行連接,后一個連接成為前一個連接的化身。TCP必須防止來自某個連接的老的重復分組在該連接已經終止后再現,從而被誤解成處於同一個連接的某個新的化身。為做到這一點,TCP將不給處於TIME_WAIT狀態的連接發起新的化身。
TIME_WAIT狀態的持續時間是MSL的2倍,使得某個方向上的分組最多存活MSL秒被丟棄,另一個方向上的應答最多存活MSL秒被丟棄,這樣保證每建立一個TCP連接的時候,來自連接先前的化身的老的重復分組都已在網絡中消逝。
那么TIME_WAIT狀態有什么危害么?
首先要明白兩個概念長連接和短連接,
短連接:
我們模擬一下TCP短連接的情況,client向server發起連接請求,server接到請求,然后雙方建立連接。client向server發送消息,server回應client,然后一次讀寫就完成了,這時候雙方任何一個都可以發起close操作,不過一般都是client先發起close操作。為什么呢,一般的server不會回復完client后立即關閉連接的,當然不排除有特殊的情況。從上面的描述看,短連接一般只會在client/server間傳遞一次讀寫操作
短連接最大的優點是方便,特別是腳本語言,由於執行完畢后腳本語言的進程就結束了,基本上都是用短連接。
但短連接最大的缺點是將占用大量的系統資源,例如:本地端口、socket句柄。
導致這個問題的原因其實很簡單:tcp協議層並沒有長短連接的概念,因此不管長連接還是短連接,連接建立->數據傳輸->連接關閉的流程和處理都是一樣的。
長連接:
接下來我們再模擬一下長連接的情況,client向server發起連接,server接受client連接,雙方建立連接。Client與server完成一次讀寫之后,它們之間的連接並不會主動關閉,后續的讀寫操作會繼續使用這個連接。
首先說一下TCP/IP詳解上講到的TCP保活功能,保活功能主要為服務器應用提供,服務器應用希望知道客戶主機是否崩潰,從而可以代表客戶使用資源。如果客戶已經消失,使得服務器上保留一個半開放的連接,而服務器又在等待來自客戶端的數據,則服務器將應遠等待客戶端的數據,保活功能就是試圖在服務器端檢測到這種半開放的連接。
如果一個給定的連接在兩小時內沒有任何的動作,則服務器就向客戶發一個探測報文段,客戶主機必須處於以下4個狀態之一:
客戶主機依然正常運行,並從服務器可達。客戶的TCP響應正常,而服務器也知道對方是正常的,服務器在兩小時后將保活定時器復位。
客戶主機已經崩潰,並且關閉或者正在重新啟動。在任何一種情況下,客戶的TCP都沒有響應。服務端將不能收到對探測的響應,並在75秒后超時。服務器總共發送10個這樣的探測 ,每個間隔75秒。如果服務器沒有收到一個響應,它就認為客戶主機已經關閉並終止連接。
客戶主機崩潰並已經重新啟動。服務器將收到一個對其保活探測的響應,這個響應是一個復位,使得服務器終止這個連接。
客戶機正常運行,但是服務器不可達,這種情況與2類似,TCP能發現的就是沒有收到探查的響應。
從上面可以看出,TCP保活功能主要為探測長連接的存活狀況,不過這里存在一個問題,存活功能的探測周期太長,還有就是它只是探測TCP連接的存活,屬於比較斯文的做法,遇到惡意的連接時,保活功能就不夠使了。
在長連接的應用場景下,client端一般不會主動關閉它們之間的連接,Client與server之間的連接如果一直不關閉的話,會存在一個問題,隨着客戶端連接越來越多,server早晚有扛不住的時候,這時候server端需要采取一些策略,如關閉一些長時間沒有讀寫事件發生的連接,這樣可以避免一些惡意連接導致server端服務受損;如果條件再允許就可以以客戶端機器為顆粒度,限制每個客戶端的最大長連接數,這樣可以完全避免某個蛋疼的客戶端連累后端服務。
長連接和短連接的產生在於client和server采取的關閉策略,具體的應用場景采用具體的策略,沒有十全十美的選擇,只有合適的選擇。
下面主要討論TIME_WAIT對短連接的影響:
正常的TCP客戶端連接在關閉后,會進入一個TIME_WAIT的狀態,持續的時間一般在14分鍾,對於連接數不高的場景,14分鍾其實並不長,對系統也不會有什么影響,
但如果短時間內(例如1s內)進行大量的短連接,則可能出現這樣一種情況:客戶端所在的操作系統的socket端口和句柄被用盡,系統無法再發起新的連接!
舉例來說:假設每秒建立了1000個短連接(Web場景下是很常見的,例如每個請求都去訪問memcached),假設TIME_WAIT的時間是1分鍾,則1分鍾內需要建立6W個短連接,
由於TIME_WAIT時間是1分鍾,這些短連接1分鍾內都處於TIME_WAIT狀態,都不會釋放,而Linux默認的本地端口范圍配置是:net.ipv4.ip_local_port_range = 32768 61000
不到3W,因此這種情況下新的請求由於沒有本地端口就不能建立了。
可以通過如下方式來解決這個問題:
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為一個很小的值;
方法2:
查看系統本地可用端口極限值
cat /proc/sys/net/ipv4/ip_local_port_range
用這條命令會返回兩個數字,默認是:32768 61000,說明這台機器本地能向外連接61000-32768=28232個連接,注意是本地向外連接,不是這台機器的所有連接,不會影響這台機器的80端口的對外連接數。但這個數字會影響到代理服務器(nginx)對app服務器的最大連接數,因為nginx對app是用的異步傳輸,所以這個環節的連接速度很快,所以堆積的連接就很少。假如nginx對app服務器之間的帶寬出了問題或是app服務器有問題,那么可能使連接堆積起來,這時可以通過設定nginx的代理超時時間,來使連接盡快釋放掉,一般來說極少能用到28232個連接。
因為有軟件使用了40000端口監聽,常常出錯的話,可以通過設定ip_local_port_range的最小值來解決:
echo "40001 61000" > /proc/sys/net/ipv4/ip_local_port_range
但是這么做很顯然把系統可用端口數減少了,這時可以把ip_local_port_range的最大值往上調,但是好習慣是使用不超過32768的端口來偵聽服務,另外也不必要去修改ip_local_port_range數值成1024 65535之類的,意義不大。
方法3:
SO_LINGER是一個socket選項,通過setsockopt API進行設置,使用起來比較簡單,但其實現機制比較復雜,且字面意思上比較難理解。
解釋最清楚的當屬《Unix網絡編程卷1》中的說明(7.5章節),這里簡單摘錄:
SO_LINGER的值用如下數據結構表示:
struct linger {undefined
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完成。
第一種情況其實和不設置沒有區別,第二種情況可以用於避免TIME_WAIT狀態,但在Linux上測試的時候,並未發現發送了RST選項,而是正常進行了四步關閉流程,
初步推斷是“只有在丟棄數據的時候才發送RST”,如果沒有丟棄數據,則走正常的關閉流程。
查看Linux源碼,確實有這么一段注釋和源碼:
=linux-2.6.37 net/ipv4/tcp.c 1915=
/* As outlined in RFC 2525, section 2.17, we send a RST here because
* data was lost. To witness the awful effects of the old behavior of
* always doing a FIN, run an older 2.1.x kernel or 2.0.x, start a bulk
* GET in an FTP client, suspend the process, wait for the client to
* advertise a zero window, then kill -9 the FTP client, wheee...
* Note: timeout is always zero in such a case.
*/
if (data_was_unread) {undefined
/* Unread data was tossed, zap the connection. */
NET_INC_STATS_USER(sock_net(sk), LINUX_MIB_TCPABORTONCLOSE);
tcp_set_state(sk, TCP_CLOSE);
tcp_send_active_reset(sk, sk->sk_allocation);
}
另外,從原理上來說,這個選項有一定的危險性,可能導致丟數據,使用的時候要小心一些,但我們在實測libmemcached的過程中,沒有發現此類現象,
應該是和libmemcached的通訊協議設置有關,也可能是我們的壓力不夠大,不會出現這種情況。
第三種情況其實就是第一種和第二種的折中處理,且當socket為非阻塞的場景下是沒有作用的。
對於應對短連接導致的大量TIME_WAIT連接問題,個人認為第二種處理是最優的選擇,libmemcached就是采用這種方式,
從實測情況來看,打開這個選項后,TIME_WAIT連接數為0,且不受網絡組網(例如是否虛擬機等)的影響。
方法4:
tcp_tw_recycle和tcp_timestamps】
參考官方文檔(http://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt),tcp_tw_recycle解釋如下:
tcp_tw_recycle選項作用為:Enable fast recycling TIME-WAIT sockets. Default value is 0.
tcp_timestamps選項作用為:Enable timestamps as defined in RFC1323. Default value is 1.
這兩個選項是linux內核提供的控制選項,和具體的應用程序沒有關系,而且網上也能夠查詢到大量的相關資料,但信息都不夠完整,最主要的幾個問題如下;
1)快速回收到底有多快?
2)有的資料說只要打開tcp_tw_recycle即可,有的又說要tcp_timestamps同時打開,具體是哪個正確?
3)為什么從虛擬機NAT出去發起客戶端連接時選項無效,非虛擬機連接就有效?
為了回答上面的疑問,只能看代碼,看出一些相關的代碼供大家參考:
=linux-2.6.37 net/ipv4/tcp_minisocks.c 269==
void tcp_time_wait(struct sock *sk, int state, int timeo)
{undefined
struct inet_timewait_sock *tw = NULL;
const struct inet_connection_sock *icsk = inet_csk(sk);
const struct tcp_sock *tp = tcp_sk(sk);
int recycle_ok = 0;
//判斷是否快速回收,這里可以看出tcp_tw_recycle和tcp_timestamps兩個選項都打開的時候才進行快速回收,
//且還有進一步的判斷條件,后面會分析,這個進一步的判斷條件和第三個問題有關
if (tcp_death_row.sysctl_tw_recycle && tp->rx_opt.ts_recent_stamp)
recycle_ok = icsk->icsk_af_ops->remember_stamp(sk);
if (tcp_death_row.tw_count < tcp_death_row.sysctl_max_tw_buckets)
tw = inet_twsk_alloc(sk, state);
if (tw != NULL) {undefined
struct tcp_timewait_sock *tcptw = tcp_twsk((struct sock *)tw);
//計算快速回收的時間,等於 RTO * 3.5,回答第一個問題的關鍵是RTO(Retransmission Timeout)大概是多少
const int rto = (icsk->icsk_rto << 2) - (icsk->icsk_rto >> 1);
//。。。。。。此處省略很多代碼。。。。。。
if (recycle_ok) {undefined
//設置快速回收的時間
tw->tw_timeout = rto;
} else {undefined
tw->tw_timeout = TCP_TIMEWAIT_LEN;
if (state == TCP_TIME_WAIT)
timeo = TCP_TIMEWAIT_LEN;
}
//。。。。。。此處省略很多代碼。。。。。。
}
RFC中有關於RTO計算的詳細規定,一共有三個:RFC-793、RFC-2988、RFC-6298,Linux的實現是參考RFC-2988。
對於這些算法的規定和Linuxde 實現,有興趣的同學可以自己深入研究,實際應用中我們只要記住Linux如下兩個邊界值:
=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
也就是說,快速回收是TIME_WAIT的狀態持續700ms,而不是正常的2MSL(Linux是1分鍾,請參考:include/net/tcp.h 109行TCP_TIMEWAIT_LEN定義)。
實測結果也驗證了這個推論,不停的查看TIME_WAIT狀態的連接,偶爾能看到1個。
最后一個問題是為什么從虛擬機發起的連接即使設置了tcp_tw_recycle和tcp_timestamps,也不會快速回收,繼續看代碼:
tcp_time_wait函數中的代碼行:recycle_ok = icsk->icsk_af_ops->remember_stamp(sk);對應的實現如下:
=linux-2.6.37 net/ipv4/tcp_ipv4.c 1772=
int tcp_v4_remember_stamp(struct sock *sk)
{undefined
//。。。。。。此處省略很多代碼。。。。。。
//當獲取對端信息時,進行快速回收,否則不進行快速回收
if (peer) {undefined
if ((s32)(peer->tcp_ts - tp->rx_opt.ts_recent) <= 0 ||
((u32)get_seconds() - peer->tcp_ts_stamp > TCP_PAWS_MSL &&
peer->tcp_ts_stamp <= (u32)tp->rx_opt.ts_recent_stamp)) {undefined
peer->tcp_ts_stamp = (u32)tp->rx_opt.ts_recent_stamp;
peer->tcp_ts = tp->rx_opt.ts_recent;
}
if (release_it)
inet_putpeer(peer);
return 1;
}
return 0;
}
上面這段代碼應該就是測試的時候虛擬機環境不會釋放的原因,當使用虛擬機NAT出去的時候,服務器無法獲取隱藏在NAT后的機器信息。
生產環境也出現了設置了選項,但TIME_WAIT連接數達到4W多的現象,可能和虛擬機有關,也可能和組網有關。
總結一下:
1)快速回收到底有多快?
局域網環境下,700ms就回收;
2)有的資料說只要打開tcp_tw_recycle即可,有的又說要tcp_timestamps同時打開,具體是哪個正確?
需要同時打開,但默認情況下tcp_timestamps就是打開的,所以會有人說只要打開tcp_tw_recycle即可;
3)為什么從虛擬機發起客戶端連接時選項無效,非虛擬機連接就有效?
和網絡組網有關系,無法獲取對端信息時就不進行快速回收;
綜合上面的分析和總結,可以看出這種方法不是很保險,在實際應用中可能受到虛擬機、網絡組網、防火牆之類的影響從而導致不能進行快速回收。
附:
1)tcp_timestamps的說明詳見RF1323,和TCP的擁塞控制(Congestion control)有關。
2)打開此選項,可能導致無法連接,請參考:http://www.pagefault.info/?p=416
方法5:
tcp_tw_reuse選項的含義如下(http://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt):
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)
{undefined
const struct tcp_timewait_sock *tcptw = tcp_twsk(sktw);
struct tcp_sock *tp = tcp_sk(sk);
/* With PAWS, it is safe from the viewpoint
of data integrity. Even without PAWS it is safe provided sequence
spaces do not overlap i.e. at data rates <= 80Mbit/sec.
Actually, the idea is close to VJ's one, only timestamp cache is
held not per host, but per port pair and TW bucket is used as state
holder.
If TW bucket has been already destroyed we fall back to VJ's scheme
and use initial timestamp retrieved from peer table.
*/
//從代碼來看,tcp_tw_reuse選項和tcp_timestamps選項也必須同時打開;否則tcp_tw_reuse就不起作用
//另外,所謂的“協議安全”,從代碼來看應該是收到最后一個包后超過1s
if (tcptw->tw_ts_recent_stamp &&
(twp == NULL || (sysctl_tcp_tw_reuse &&
get_seconds() - tcptw->tw_ts_recent_stamp > 1))) {undefined
tp->write_seq = tcptw->tw_snd_nxt + 65535 + 2;
if (tp->write_seq == 0)
tp->write_seq = 1;
tp->rx_opt.ts_recent = tcptw->tw_ts_recent;
tp->rx_opt.ts_recent_stamp = tcptw->tw_ts_recent_stamp;
sock_hold(sktw);
return 1;
}
return 0;
}
總結一下:
1)tcp_tw_reuse選項和tcp_timestamps選項也必須同時打開;
2)重用TIME_WAIT的條件是收到最后一個包后超過1s。
官方手冊有一段警告:
It should not be changed without advice/request of technical
experts.
對於大部分局域網或者公司內網應用來說,滿足條件2)都是沒有問題的,因此官方手冊里面的警告其實也沒那么可怕:)
方法6:
參考官方文檔(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。
通過源碼查看發現,這個選項比較簡單,其實現代碼如下:
=linux-2.6.37 net/ipv4/tcp_minisocks.c 269==
void tcp_time_wait(struct sock *sk, int state, int timeo)
{undefined
struct inet_timewait_sock *tw = NULL;
const struct inet_connection_sock *icsk = inet_csk(sk);
const struct tcp_sock *tp = tcp_sk(sk);
int recycle_ok = 0;
if (tcp_death_row.sysctl_tw_recycle && tp->rx_opt.ts_recent_stamp)
recycle_ok = icsk->icsk_af_ops->remember_stamp(sk);
if (tcp_death_row.tw_count < tcp_death_row.sysctl_max_tw_buckets)
tw = inet_twsk_alloc(sk, state);
if (tw != NULL) {undefined
//分配成功,進行TIME_WAIT狀態處理,此處略去很多代碼
else {undefined
//分配失敗,不進行處理,只記錄日志: TCP: time wait bucket table overflow
/* Sorry, if we're out of memory, just CLOSE this
* socket up. We've got bigger problems than
* non-graceful socket closings.
*/
NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPTIMEWAITOVERFLOW);
}
tcp_update_metrics(sk);
tcp_done(sk);
}
實測結果驗證,配置為100,TIME_WAIT連接數就穩定在100,且不受組網和其它配置的影響。
官方手冊中有一段警告:
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攻擊,我們不應該人工減少,如果網絡條件需要的話,反而應該增加。
但其實對於我們的局域網或者公司內網應用來說,這個風險並不大。