之所以起這樣乙個題目是因為很久以前我曾經寫過一篇介紹time_wait的文章,不過當時基本屬於淺嘗輒止,並沒深入說明問題的來龍去脈,碰巧這段時間反覆被別人問到相關的問題,讓我覺得有必要全面總結一下,以備不時之需。
討論前大家可以拿手頭的伺服器摸摸底,記住「ss」比「netstat」快:
shell> ss -ant | awk '如果你只是想單獨查詢一下time_wait的數量,那麼還可以更簡單一些:nr>1 end
'
shell> cat /proc/net/sockstat我猜你一定被巨大無比的time_wait網路連線總數嚇到了!以我個人的經驗,對於一台繁忙的web伺服器來說,如果主要以短連線為主,那麼其time_wait網路連線總數很可能會達到幾萬,甚至十幾萬。雖然乙個time_wait網路連線耗費的資源無非就是乙個埠、一點記憶體,但是架不住基數大,所以這始終是乙個需要面對的問題。
tcp在建立連線的時候需要握手,同理,在關閉連線的時候也需要握手。為了更直觀的說明關閉連線時握手的過程,我們引用「the tcp/ip guide」中的例子:
tcp close
因為tcp連線是雙向的,所以在關閉連線的時候,兩個方向各自都需要關閉。先發fin包的一方執行的是主動關閉;後發fin包的一方執行的是被動關閉。主動關閉的一方會進入time_wait狀態,並且在此狀態停留兩倍的msl時長。
穿插一點msl的知識:msl指的是報文段的最大生存時間,如果報文段在網路活動了msl時間,還沒有被接收,那麼會被丟棄。關於msl的大小,rfc 793協議中給出的建議是兩分鐘,不過實際上不同的作業系統可能有不同的設定,以linux為例,通常是半分鐘,兩倍的msl就是一分鐘,也就是60秒,並且這個數值是硬編碼在核心中的,也就是說除非你重新編譯核心,否則沒法修改它:
#define tcp_timewait_len (60*hz)如果每秒的連線數是一千的話,那麼一分鐘就可能會產生六萬個time_wait。
為什麼主動關閉的一方不直接進入closed狀態,而是進入time_wait狀態,並且停留兩倍的msl時長呢?這是因為tcp是乙個建立在不可靠網路上的可靠的協議,主動關閉的一方收到被動關閉的一方發出的fin包後,回應ack包,同時進入time_wait狀態,但是因為網路原因,主動關閉的一方傳送的這個ack包很可能延遲,從而觸發被動連線一方重傳fin包。極端情況下,這一去一回,就是兩倍的msl時長。如果主動關閉的一方跳過time_wait直接進入closed,或者在time_wait停留的時長不足兩倍的msl,那麼當被動關閉的一方早先發出的延遲包到達後,就可能出現類似下面的問題:
不管是哪種情況都會讓tcp不在可靠,所以time_wait狀態有存在的必要性。
從前面的描述我們可以得出這樣的結論:time_wait這東西沒有的話不行,有的話太多也是個麻煩事。下面讓我們看看有哪些方法可以控制time_wait數量,這裡只說一些常規方法,另外一些諸如so_linger之類的方法太過偏門,略過不談。
ip_conntrack:顧名思義就是跟蹤連線。一旦啟用了此模組,就能在系統引數裡發現很多用來控制網路連線狀態超時的設定,其中自然也包括time_wait:
shell> modprobe ip_conntrack我們可以嘗試縮小它的設定,比如十秒,甚至一秒,具體設定成多少合適取決於網路情況而定,當然也可以參考相關的案例。不過就我的個人意見來說,ip_conntrack引入的問題比解決的還多,比如效能會大幅下降,所以不建議使用。shell> sysctl net.ipv4.netfilter.ip_conntrack_tcp_timeout_time_wait
tcp_tw_recycle:顧名思義就是**time_wait連線。可以說這個核心引數已經變成了大眾處理time_wait的萬金油,如果你在網路上搜尋time_wait的解決方案,十有**會推薦設定它,不過這裡隱藏著乙個不易察覺的陷阱:
當多個客戶端通過nat方式聯網並與服務端互動時,服務端看到的是同乙個ip,也就是說對服務端而言這些客戶端實際上等同於乙個,可惜由於這些客戶端的時間戳可能存在差異,於是乎從服務端的視角看,便可能出現時間戳錯亂的現象,進而直接導致時間戳小的資料報被丟棄。參考:tcp_tw_recycle和tcp_timestamps導致connect失敗問題。
tcp_tw_reuse:顧名思義就是復用time_wait連線。當建立新連線的時候,如果可能的話會考慮復用相應的time_wait連線。通常認為「tcp_tw_reuse」比「tcp_tw_recycle」安全一些,官方文件裡是這樣說的:如果從協議視角看它是安全的,那麼就可以使用。這簡直就是外交辭令啊!按我的看法,如果網路比較穩定,比如都是內網連線,那麼就可以嘗試使用,畢竟此時出現前面提的延遲包的可能性微乎其微。
不過需要注意的是在**使用,既然我們要復用連線,那麼當然應該在連線的發起方使用,而不能在被連線方使用。舉例來說:客戶端向服務端發起http請求,服務端響應後主動關閉連線,於是time_wait便留在了服務端,此類情況使用「tcp_tw_reuse」是無效的,因為服務端是被連線方,所以不存在復用連線一說。讓我們延伸一點來看,比如說服務端是php,它查詢另乙個mysql服務端,然後主動斷開連線,於是time_wait就落在了php一側,此類情況下使用「tcp_tw_reuse」是有效的,因為此時php相對於mysql而言是客戶端,它是連線的發起方,所以可以復用連線。
tcp_max_tw_buckets:顧名思義就是控制time_wait總數。官網文件說這個選項只是為了阻止一些簡單的dos攻擊,平常不要人為的降低它。不過我覺得要分清主要矛盾是什麼,如果time_wait已經成為最棘手的問題,那麼即便此時並不是dos攻擊的場景,我們也可以嘗試通過設定它來緩解主要矛盾。
通過設定它,系統會將多餘的time_wait刪除掉,此時系統日誌裡可能會顯示:「tcp: time wait bucket table overflow」,多數情況下不用太在意這些資訊。
需要提醒大家的是物極必反,曾經看到有人把「tcp_max_tw_buckets」設定成0,也就是說完全拋棄time_wait,這就有些冒險了,用一句圍棋諺語來說:入界宜緩。
…有時候,如果我們換個角度去看問題,往往能得到四兩撥千斤的效果。前面提到的例子:客戶端向服務端發起http請求,服務端響應後主動關閉連線,於是time_wait便留在了服務端。這裡的關鍵在於主動關閉連線的是服務端!在關閉tcp連線的時候,先出手的一方注定逃不開time_wait的宿命,套用一句歌詞:把我的悲傷留給自己,你的美麗讓你帶走。如果客戶端可控的話,那麼在服務端開啟keepalive,保證服務端不會主動關閉連線,讓客戶端主動關閉連線,如此一來問題便迎刃而解了。
處理 TIME WAIT 過多
netstat n awk tcp end last ack 14 syn recv 348 established 32 fin wait1 239 fin wait2 30 closing 31 time wait 1643 狀態描述 closed 無連線是活動的或正在進行 listen 伺服器...
運維小記 TIME WAIT過多的處理方法
sysctl 8 是乙個允許您改變正在執行中的 freebsd 系統的介面。它包含一些 tcp ip 堆疊和虛擬記憶體系統的高階選項,這可以讓有經驗的管理員提高引人注目的系統效能。用 sysctl 8 可以讀取設定超過五百個系統變數。基於這點,sysctl 8 提供兩個功能 讀取和修改系統設定。檢視...
TIME WAIT過多的問題
netstat ant awk tcp end last ack 14 syn recv 348 established 70 fin wait1 229 fin wait2 30 closing 33 time wait 18122 命令解釋 closed 無連線是活動的或正在進行 listen ...