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