天天看點

記一次time_wait & close_wait的讨論總結

作者:牧原
記一次time_wait & close_wait的讨論總結

TIME_WAIT是TCP連接配接關閉過程中的一個狀态,具體是這麼形成的:

1 主動關閉端A:發FIN,進入FIN-WAIT-1狀态,并等待......

2 被動關閉端P:收到FIN後必須立即發ACK,進入CLOSE_WAIT狀态,并等待......

3 主動關閉端A:收到ACK後進入FIN-WAIT-2狀态,并等待......

4 被動關閉端P:發FIN,進入LAST_ACK狀态,并等待......

5 主動關閉端A:收到FIN後必須立即發ACK,進入TIME_WAIT狀态,等待2MSL後結束Socket

6 被動關閉端P:收到ACK後結束Socket

是以,TIME_WAIT狀态是出現在主動發起連接配接關閉的一點,和是誰發起的連接配接無關,可以是client端,也可以是server端。

而從TIME_WAIT狀态到CLOSED狀态,有一個逾時設定,這個逾時設定是 2*MSL(RFC793定義了MSL為2分鐘,Linux設定成了30s)

為什麼需要TIME_WAIT?

主要有兩個原因:

1)為了確定兩端能完全關閉連接配接。

假設A伺服器是主動關閉連接配接方,B伺服器是被動方。如果沒有TIME_WAIT狀态,A伺服器發出最後一個ACK就進入關閉狀态,如果這個ACK對端沒有收到,對端就不能完成關閉。對端沒有收到ACK,會重發FIN,此時連接配接關閉,這個FIN也得不到ACK,而有TIME_WAIT,則會重發這個ACK,確定對端能正常關閉連接配接。

2)為了確定後續的連接配接不會收到“髒資料”

剛才提到主動端進入TIME_WAIT後,等待2MSL後CLOSE,這裡的MSL是指(maximum segment lifetime,我們核心一般是30s,2MSL就是1分鐘),網絡上資料包最大的生命周期。這是為了使網絡上由于重傳出現的old duplicate segment都消失後,才能建立參數(四元組,源IP/PORT,目标IP/PORT)相同的連接配接,如果等待時間不夠長,又建立好了一樣的連接配接,再收到old duplicate segment,資料就錯亂了。

TIME_WAIT 會導緻什麼問題

1) 建立連接配接失敗

TIME_WAIT到CLOSED,需要2MSL=60s的時間。這個時間非常長。每個連接配接在業務結束之後,需要60s的時間才能完全釋放。如果業務上采用的是短連接配接的方式,會導緻非常多的TIME_WAIT狀态的連接配接,會占用一些資源,主要是本地端口資源。

一台伺服器的本地可用端口是有限的,也就幾萬個端口,由這個參數控制:

sysctl net.ipv4.ip_local_port_range

net.ipv4.ip_local_port_range = 32768 61000

當伺服器存在非常多的TIME_WAIT連接配接,将本地端口都占用了,就不能主動發起新的連接配接去連其他伺服器了。

這裡需要注意,是主動發起連接配接,又是主動發起關閉的一方才會遇到這個問題。

如果是server端主動關閉client端建立的連接配接産生了大量的TIME_WAIT連接配接,這是不會出現這個問題的。除非是其中涉及到的某個用戶端的TIME_WAIT連接配接都有好幾萬個了。

2)TIME_WAIT條目超出限制

這個限制,是由一個核心參數控制的:

sysctl net.ipv4.tcp_max_tw_buckets

net.ipv4.tcp_max_tw_buckets = 5000

超出了這個限制會報一條INFO級别的核心日志,然後繼續關閉掉連接配接。并沒有什麼特别大的影響,隻是增加了剛才提到的收到髒資料的風險而已。

另外的風險就是,關閉掉TIME_WAIT連接配接後,剛剛發出的ACK如果對端沒有收到,重發FIN包出來時,不能正确回複ACK,隻是回複一個RST包,導緻對端程式報錯,說connection reset。

是以net.ipv4.tcp_max_tw_buckets這個參數是建議不要改小的,改小會帶來風險,沒有什麼收益,隻是表面上通過netstat看到的TIME_WAIT少了些而已,有啥用呢?

并且,建議是當遇到條目不夠,增加這個值,僅僅是浪費一點點記憶體而已。

如何解決time_wait?

1)最佳方案是應用改造長連接配接,但是一般不太适用

2)修改系統回收參數

設定以下參數

net.ipv4.tcp_timestamps = 1

net.ipv4.tcp_tw_recycle = 1

設定該參數會帶來什麼問題?

如果這兩個參數同時開啟,會校驗源ip過來的包攜帶的timestamp是否遞增,如果不是遞增的話,則會導緻三次握手建聯不成功,具體表現為抓包的時候看到syn發出,server端不響應syn ack

通俗一些來講就是,一個區域網路有多個用戶端通路您,如果有用戶端的時間比别的用戶端時間慢,就會建聯不成功

治标不治本的方式:

放大端口範圍

放大time_wait的buckets

net.ipv4.tcp_max_tw_buckets = 180000

----------update 2020-02-22 關于tw_bucket之争------------

關于net.ipv4.tcp_max_tw_buckets到底要不要放大,目前雲上ecs多數是設定了5000,我個人淺見覺得小了

記一次time_wait & close_wait的讨論總結

簡單來說 net.ipv4.tcp_max_tw_buckets的作用 是為了“優雅”的關閉連接配接

1,完整的關閉連接配接

2,避免有資料包重複

如果tw滿了會怎樣

TCP: time wait bucket table overflow

新核心

tw_bucket滿了的話,會影響established狀态的連接配接在finack的時候,直接進入closed狀态

老核心

tw_bucket滿了的話,會将tw_bucket裡面的time_wait按照一定的規則(如LRU),将一批time_Wait直接進入closed狀态 ,然後established狀态發送finack後進入time_wait

tw的開銷是什麼?

1,特别少量的記憶體

2,占用本地端口

tw放大的好與壞?

1,放大的話需要更多的記憶體開銷,但是幾乎可以忽略不計

2,占用更多的本地端口,需要适當的放大學地端口範圍,端口範圍經過簡單的測試,建議設定為tw的1.5倍

net.ipv4.ip_local_port_range

3,netstat 大量的掃描socket的時候(ss不會掃描,但是ss在slab記憶體特别高的時候,也有可能會引起抖動),極端情況下可能會引起性能抖動

4,tw放大,local_port_range放大,還可以配置複用以及快速回收等參數

5,使用快速回收可能會導緻snat時間戳遞增校驗問題,不遞增的話syn不響應

特殊場景的時候(本機會發起大量短連結的時候)

1, nginx結合php-fpm需要本地起端口,

2,nginx反代如(java ,容器等)

如下圖所示,

tcp_tw_reuse參數需要結合net.ipv4.tcp_timestamps = 1 一起來用

即 伺服器即做用戶端,也做server端的時候

tcp_tw_reuse參數用來設定是否可以在新的連接配接中重用TIME_WAIT狀态的套接字。注意,重用的是TIME_WAIT套接字占用的端口号,而不是TIME_WAIT套接字的記憶體等。這個參數對用戶端有意義,在主動發起連接配接的時候會在調用的inet_hash_connect()中會檢查是否可以重用TIME_WAIT狀态的套接字。如果你在伺服器段設定這個參數的話,則沒有什麼作用,因為伺服器端ESTABLISHED狀态的套接字和監聽套接字的本地IP、端口号是相同的,沒有重用的概念。但并不是說伺服器端就沒有TIME_WAIT狀态套接字。

是以 該類場景最終建議是

net.ipv4.tcp_tw_recycle = 0 關掉快速回收
net.ipv4.tcp_tw_reuse = 1   開啟tw狀态的端口複用(用戶端角色)
net.ipv4.tcp_timestamps = 1 複用需要timestamp校驗為1 
net.ipv4.tcp_max_tw_buckets = 30000 放大bucket
net.ipv4.ip_local_port_range = 15000 65000 放大學地端口範圍           

記憶體開銷測試

# ss -s
Total: 15254 (kernel 15288)
TCP:   15169 (estab 5, closed 15158, orphaned 0, synrecv 0, timewait 3/0), ports 0
Transport Total     IP        IPv6
*         15288     -         -        
RAW       0         0         0        
UDP       5         4         1        
TCP       11        11        0        
INET      16        15        1        
FRAG      0         0         0        
15000個socket消耗30多m記憶體           
記一次time_wait & close_wait的讨論總結

----------萬惡的分割線-----------

關于 close_wait

記一次time_wait & close_wait的讨論總結

如上所示,CLOSE_WAIT的狀态是 伺服器端/用戶端程式收到外部過來的FIN之後,響應了ACK包,之後就進入了 CLOSE_WAIT 狀态。一般來說,如果一切正常,稍後伺服器端/用戶端程式 需要發出 FIN 包,進而遷移到 LAST_ACK 狀态,收到對端過來的ACK後,完成TCP連接配接關閉的整個過程。

注:不管是伺服器還是用戶端,隻要是被動接收第一個FIN的那一方才會進入CLOSE_WAIT狀态