原因1:防止連接關閉時四次揮手中的最后一次ACK丟失:
TCP需要保證每一包數據都可靠的到達對端,包括正常連接狀態下的業務數據報文,以及用于連接管理的握手、揮手報文,這其中在四次揮手中的最后一次ACK報文比較特殊,TIME_WAIT狀態就是為了應對最后一條ACK丟失的情況。
TCP保證可靠傳輸的前提是收發兩端分別維護關于這條連接的狀態信息(TCB控制塊),當發生丟包時進行ARQ重傳。如果連接釋放了,就無法進行重傳,也就無法保證發生丟包時的可靠傳輸。
對于最后一條ACK,如果沒有TIME_WAIT狀態,主動關閉一方(客戶端)就會在收到對端(服務器)的FIN并回復ACK后 直接從FIN_WAIT_2 進入 CLOSED狀態,并釋放連接,銷毀TCB實例。此時如果最后一條ACK丟失,那么服務器重傳的FIN將無人處理,最后導致服務器長時間的處于 LAST_ACK狀態而無法正常關閉(服務器只能等到達到FIN的最大重傳次數后關閉)。
至于將TIME_WAIT的時長設置為 2MSL,是因為報文在鏈路中的最大生存時間為MSL(Maximum Segment Lifetime),超過這個時長后報文就會被丟棄。TIME_WAIT的時長則是:最后一次ACK傳輸到服務器的時間 + 服務器重傳FIN 的時間,即為 2MSL。
原因2:防止新連接收到舊鏈接的TCP報文:
TCP使用四元組區分一個連接(源端口、目的端口、源IP、目的IP),如果新、舊連接的IP與端口號完全一致,則內核協議棧無法區分這兩條連接。
2*MSL 的時間足以保證兩個方向上的數據都被丟棄,使得原來連接的數據包在網絡中都自然消失,再出現的數據包一定是新連接上產生的。
MSL(Maximum Segment Lifetime) 報文最大生存時間在不同操作系統中的具體值:
Linux(Ubuntu) : 60 s
Unix : 30 s
2. TIME_WAIT對連接并發數的影響(TIME_WAIT過多的危害):
在Linux系統中,MSL = 60 s, 2 * MSL = 120 s,所以一條待關閉的TCP連接會在 TIME_WAIT 狀態等待 120秒(2分鐘)。
當連接處于TIME_WAIT狀態時仍會占用系統資源(fd、端口、內存),當系統的并發連接數很大時,過多的TIME_WAIT狀態的連接會對系統的并發量造成影響。
(1)對服務器的影響:
由于服務器一般只需要監聽一個固定的端口,所以服務器所能支持的最大并發出數的上限取決于系統套接字描述符fd的大小,以及服務器的內存大小。
fd:
Linux中一個進程 所能打開的fd的最大數量默認為 1024 個,可通過 "ulimit -n (+指定數量)" 進行修改。
Linux系統所能支持的fd最大值在 /proc/sys/fs/fd-max 文件中可以查看,系統當前的fd使用情況可以通過 /proc/sys/fs/fd-nr 查看。(本機上的fd-max的值為:1221842,即100W級。實際上fd的最大值同樣也取決于系統內存的大小)
內存:
假設每一個TCP連接需要開辟 “4k的接收緩沖區 + 4k的發送緩沖區 = 8k”,1W的并發連接需要80M內存,10W并發需要800M,100W并發需要8G內存。
綜上,服務器的并發數主要受限于系統內存的大小,當 TIME_WAIT 狀態的連接過多時,會導致消耗的內存增加,這一點可以通過擴展服務器的內存來解決。
(2)對客戶端的影響:
客戶端的并發數主要受限于端口數量。
一種典型的場景是:高并發短連接(“短連接”表示“業務處理+傳輸數據”的時間遠遠小于TIME_WAIT超時的時間)。
在這種場景下,客戶端可能會消耗大量的端口(例如取一個Web網頁,1秒鐘的HTTP短連接處理完業務數據,卻需要 2分鐘的TIME_WAIT等待時間,在這段時間內客戶端上的這個端口是無法被其他連接使用的,如果新建連接則需要使用另外的端口號),Linux系統的最大端口為65535,除去系統使用的端口號,假設網絡進程可使用的端口有 6W個,由于TIME_WAIT狀態下在 2*MSL(120秒)內無法再被使用,這就限制了客戶端的連接速率為 60000 / 120秒 = 500 次/秒, 這是一個非常低的并發率。
同時,大量的TIME_WAIT連接同樣會消耗客戶端的內存,所以客戶端的最大并發數取決于 端口號與內存 二者中的最小值。
3. 優化TIME_WAIT的方法:
方法1:修改內核參數 tcp_tw_reuse:
net.ipv4.tcp_timestamp = 1;
注意:tcp_tw_reuse 內核參數只在調用 connect() 函數時起作用,所以只能用于客戶端(主動連接的一端)。
tcp_tw_reuse 的作用是:在調用connect()函數時,內核會隨機找一個處于TIME_WAIT狀態 超過1秒 的連接給新連接復用。(超時時間由 tcp_timestamp設置,默認為 1秒)
這種方式可以縮短 TIME_WAIT 的等待時間。
方法2:修改內核參數 tcp_max_tw_buckets:
net.ipv4.tcp_max_tw_buckets 參數的默認值為18000,當系統中處于 TIME_WAIT 狀態的連接數量超過閾值,系統會將后面的TIME_WAIT連接重置。
由于這種方法會直接重置連接,因此需要謹慎使用。
方法3:設置套接字選項 SO_LINGER:
SO_LINGER選項用于設置 調用close() 關閉TCP連接時的行為,注意 SO_LINGER選項會使用RST復位報文段取代 FIN-ACK四次揮手的過程,設置了SO_LINGER選項的一方在調用close() 時會直接發送一個RST,接收端收到后復位連接,不會回復任何響應。
這樣做的弊端是導致TCP緩沖區中的數據被丟棄。
正常情況下,調用close后的缺省行為是:如果有待發送的數據殘留在發送緩沖區中,內核協議棧將繼續將這些數據發送給接收端后才關閉連接,走正常的四次揮手流程;
設置SO_LINGER后,立即關閉連接,通過RST分組,發送緩沖區如果有未發送的數據,將會被丟棄,主動關閉的一方跳過TIME_WAIT狀態,直接進入CLOSED(也跳過了FIN_WAIT_1 和 FIN_WAIT_2)。
SO_LINGER的另一種更溫和的實現方式是設置一個超時時間(so_linger.l_linger),而不是直接關閉。
應用程序調用close后進入睡眠,內核協議棧負責發送緩沖中殘留的待發送數據,如果在 l_linger 超時時間內發送完畢,則走正常的四次揮手流程;如果超時未發送完,則發送RST強制關閉連接,并丟棄發送緩沖區中其余的數據(接收端收到RST后也會丟棄接收緩沖區中的數據),并且發送端close()函數返回 EWOULDBLOCK。
綜上,如果只是單純為了規避 TIME_WAIT 狀態,使用 SO_LINGER并不是一個好主意,因為它會在調用close關閉連接時 使用RST強制關閉連接,這可能會導致 發送緩沖區、接收緩沖區 中還未處理完的數據被丟棄。
so_linger.l_onoff = 1; //0表示關閉,忽略l_linger的值;非0表示打開
so_linger.l_linger = 0; //設置等待時間,等于0則表示立即關閉
setsockopt(fd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof(so_linger));
方法4:設置套接字選項 SO_REUSEADDR:
SO_REUSEADDR 選項用于通知內核:
如果端口忙,并且端口對應的TCP連接狀態為TIME_WAIT,則可以重用端口;
如果端口忙,并且端口對應的TCP連接處于其他狀態(非TIME_WAIT),則返回 “Address already in use” 的錯誤信息。
設置SO_REUSEADDR的風險是可能會導致新連接上收到舊連接的數據(復用了舊連接的端口,導致新舊連接的四元組完全一致,內核協議棧無法區分這兩個連接)。
SO_REUSEADDR 選項并沒像 tcp_tw_reuse 那樣同時提供一個 tcp_timestamp 參數可以設置 TIME_WAIT的等待時長。
綜上,對TIME_WAIT狀態的優化思路是盡量縮小等待時長,而不是暴力的直接關閉(可能會引起新連接收到舊連接數據的風險),也不要直接發送RST復位連接(可能會引起發送、接收緩沖區中的數據丟失),所以使用修改內核參數 tcp_tw_reuse 參數是最保險的方式,通過根據實際網絡情況和應用場景適當的調節 tcp_timestamp 的值,可以達到縮小 TIME_WAIT 等待時長,進而減少系統中同一時刻處于 TIME_WAIT 狀態的連接數量的目的。
-
數據
+關注
關注
8文章
7140瀏覽量
89581 -
操作系統
+關注
關注
37文章
6895瀏覽量
123748 -
TIME
+關注
關注
0文章
13瀏覽量
14335
發布評論請先 登錄
相關推薦
評論