10 TIME_WAIT介紹

在前面的基礎篇里表锻,我們對網絡編程涉及到的基礎知識進行了梳理精钮,主要內容包括 C/S 編程模型威鹿、TCP 協議、UDP 協議和本地套接字等內容轨香。在提高篇里忽你,我將結合我的經驗,引導你對 TCP 和 UDP 進行更深入的理解臂容。

學習完提高篇之后科雳,我希望你對如何提高 TCP 及 UDP 程序的健壯性有一個全面清晰的認識,從而為深入理解性能篇打下良好的基礎脓杉。

在前面的基礎篇里糟秘,我們了解了 TCP 四次揮手,在四次揮手的過程中球散,發(fā)起連接斷開的一方會有一段時間處于 TIME_WAIT 的狀態(tài)蚌堵,你知道 TIME_WAIT 是用來做什么的么?在面試和實戰(zhàn)中沛婴,TIME_WAIT 相關的問題始終是繞不過去的一道難題吼畏。下面就請跟隨我,一起找出隱藏在細節(jié)下的魔鬼吧嘁灯。

TIME_WAIT 發(fā)生的場景

讓我們先從一例線上故障說起泻蚊。在一次升級線上應用服務之后,我們發(fā)現該服務的可用性變得時好時壞丑婿,一段時間可以對外提供服務性雄,一段時間突然又不可以没卸,大家都百思不得其解。運維同學登錄到服務所在的主機上秒旋,使用 netstat 命令查看后才發(fā)現约计,主機上有成千上萬處于 TIME_WAIT 狀態(tài)的連接。

經過層層剖析后迁筛,我們發(fā)現罪魁禍首就是 TIME_WAIT煤蚌。為什么呢?我們這個應用服務需要通過發(fā)起 TCP 連接對外提供服務细卧。每個連接會占用一個本地端口尉桩,當在高并發(fā)的情況下,TIME_WAIT 狀態(tài)的連接過多贪庙,多到把本機可用的端口耗盡蜘犁,應用服務對外表現的癥狀,就是不能正常工作了止邮。當過了一段時間之后这橙,處于 TIME_WAIT 的連接被系統(tǒng)回收并關閉后,釋放出本地端口可供使用导披,應用服務對外表現為析恋,可以正常工作。這樣周而復始盛卡,便會出現了一會兒不可以,過一兩分鐘又可以正常工作的現象筑凫。

那么為什么會產生這么多的 TIME_WAIT 連接呢滑沧?

這要從 TCP 的四次揮手說起。我在文稿中放了這樣一張圖巍实。

TCP四次揮手

CP 連接終止時滓技,主機 1 先發(fā)送 FIN 報文,主機 2 進入 CLOSE_WAIT 狀態(tài)棚潦,并發(fā)送一個 ACK 應答令漂,同時,主機 2 通過 read 調用獲得 EOF丸边,并將此結果通知應用程序進行主動關閉操作叠必,發(fā)送 FIN 報文。主機 1 在接收到 FIN 報文后發(fā)送 ACK 應答妹窖,此時主機 1 進入 TIME_WAIT 狀態(tài)纬朝。

主機 1 在 TIME_WAIT 停留持續(xù)時間是固定的,是最長分節(jié)生命期 MSL(maximum segment lifetime)的兩倍骄呼,一般稱之為 2MSL共苛。和大多數 BSD 派生的系統(tǒng)一樣判没,Linux 系統(tǒng)里有一個硬編碼的字段,名稱為TCP_TIMEWAIT_LEN隅茎,其值為 60 秒澄峰。也就是說,Linux 系統(tǒng)停留在 TIME_WAIT 的時間為固定的 60 秒辟犀。

#define TCP_TIMEWAIT_LEN (60*HZ) 
/* how long to wait to destroy TIME-WAIT state, about 60 seconds  */

過了這個時間之后俏竞,主機 1 就進入 CLOSED 狀態(tài)。為什么是這個時間呢踪蹬?你可以先想一想胞此,稍后我會給出解答。

你一定要記住一點跃捣,只有發(fā)起連接終止的一方會進入 TIME_WAIT 狀態(tài)漱牵。這一點面試的時候經常會被問到。

TIME_WAIT 的作用

你可能會問疚漆,為什么不直接進入 CLOSED 狀態(tài)酣胀,而要停留在 TIME_WAIT 這個狀態(tài)?

這要從兩個方面來說娶聘。

首先闻镶,這樣做是為了確保最后的 ACK 能讓被動關閉方接收,從而幫助其正常關閉丸升。

TCP 在設計的時候铆农,做了充分的容錯性設計,比如狡耻,TCP 假設報文會出錯墩剖,需要重傳。在這里夷狰,如果圖中主機 1 的 ACK 報文沒有傳輸成功岭皂,那么主機 2 就會重新發(fā)送 FIN 報文。

如果主機 1 沒有維護 TIME_WAIT 狀態(tài)沼头,而直接進入 CLOSED 狀態(tài)爷绘,它就失去了當前狀態(tài)的上下文,只能回復一個 RST 操作进倍,從而導致被動關閉方出現錯誤土至。

現在主機 1 知道自己處于 TIME_WAIT 的狀態(tài),就可以在接收到 FIN 報文之后猾昆,重新發(fā)出一個 ACK 報文毙籽,使得主機 2 可以進入正常的 CLOSED 狀態(tài)。

第二個理由和連接“化身”和報文迷走有關系毡庆,為了讓舊連接的重復分節(jié)在網絡中自然消失坑赡。

我們知道烙如,在網絡中,經常會發(fā)生報文經過一段時間才能到達目的地的情況毅否,產生的原因是多種多樣的亚铁,如路由器重啟,鏈路突然出現故障等螟加。如果迷走報文到達時徘溢,發(fā)現 TCP 連接四元組(源 IP,源端口捆探,目的 IP然爆,目的端口)所代表的連接不復存在,那么很簡單黍图,這個報文自然丟棄曾雕。

我們考慮這樣一個場景,在原連接中斷后助被,又重新創(chuàng)建了一個原連接的“化身”剖张,說是化身其實是因為這個連接和原先的連接四元組完全相同,如果迷失報文經過一段時間也到達揩环,那么這個報文會被誤認為是連接“化身”的一個 TCP 分節(jié)搔弄,這樣就會對 TCP 通信產生影響。

和連接“化身”的報文迷走

所以丰滑,TCP 就設計出了這么一個機制顾犹,經過 2MSL 這個時間,足以讓兩個方向上的分組都被丟棄褒墨,使得原來連接的分組在網絡中都自然消失炫刷,再出現的分組一定都是新化身所產生的。

劃重點貌亭,2MSL 的時間是從主機 1 接收到 FIN 后發(fā)送 ACK 開始計時的;如果在 TIME_WAIT 時間內认臊,因為主機 1 的 ACK 沒有傳輸到主機 2圃庭,主機 1 又接收到了主機 2 重發(fā)的 FIN 報文,那么 2MSL 時間將重新計時失晴。道理很簡單剧腻,因為 2MSL 的時間,目的是為了讓舊連接的所有報文都能自然消亡涂屁,現在主機 1 重新發(fā)送了 ACK 報文书在,自然需要重新計時,以便防止這個 ACK 報文對新可能的連接化身造成干擾拆又。

TIME_WAIT 的危害

過多的 TIME_WAIT 的主要危害有兩種儒旬。

第一是內存資源占用栏账,這個目前看來不是太嚴重,基本可以忽略栈源。

第二是對端口資源的占用挡爵,一個 TCP 連接至少消耗一個本地端口。要知道甚垦,端口資源也是有限的茶鹃,一般可以開啟的端口為 32768~61000 ,也可以通過net.ipv4.ip_local_port_range指定艰亮,如果 TIME_WAIT 狀態(tài)過多闭翩,會導致無法創(chuàng)建新連接。這個也是我們在一開始講到的那個例子迄埃。

如何優(yōu)化 TIME_WAIT疗韵?

在高并發(fā)的情況下,如果我們想對 TIME_WAIT 做一些優(yōu)化调俘,來解決我們一開始提到的例子伶棒,該如何辦呢?

net.ipv4.tcp_max_tw_buckets

一個暴力的方法是通過 sysctl 命令彩库,將系統(tǒng)值調小肤无。這個值默認為 18000,當系統(tǒng)中處于 TIME_WAIT 的連接一旦超過這個值時骇钦,系統(tǒng)就會將所有的 TIME_WAIT 連接狀態(tài)重置宛渐,并且只打印出警告信息。這個方法過于暴力眯搭,而且治標不治本窥翩,帶來的問題遠比解決的問題多,不推薦使用鳞仙。

調低 TCP_TIMEWAIT_LEN寇蚊,重新編譯系統(tǒng)

這個方法是一個不錯的方法,缺點是需要“一點”內核方面的知識棍好,能夠重新編譯內核仗岸。我想這個不是大多數人能接受的方式。

SO_LINGER 的設置

英文單詞“l(fā)inger”的意思為停留借笙,我們可以通過設置套接字選項扒怖,來設置調用 close 或者 shutdown 關閉連接時的行為。

int  setsockopt(int sockfd, int level, int optname, const void *optval,
        socklen_t optlen);
struct  linger {
 int  l_onoff;    /* 0=off, nonzero=on */
 int  l_linger;    /* linger time, POSIX specifies units as seconds */
}

設置 linger 參數有幾種可能:

  • 如果l_onoff為 0业稼,那么關閉本選項盗痒。l_linger的值被忽略,這對應了默認行為低散,close 或 shutdown 立即返回俯邓。如果在套接字發(fā)送緩沖區(qū)中有數據殘留骡楼,系統(tǒng)會將試著把這些數據發(fā)送出去。

  • 如果l_onoff為非 0看成, 且l_linger值也為 0君编,那么調用 close 后,會立該發(fā)送一個 RST 標志給對端川慌,該 TCP 連接將跳過四次揮手吃嘿,也就跳過了 TIME_WAIT 狀態(tài),直接關閉梦重。這種關閉的方式稱為“強行關閉”兑燥。 在這種情況下,排隊數據不會被發(fā)送琴拧,被動關閉方也不知道對端已經徹底斷開降瞳。只有當被動關閉方正阻塞在recv()調用上時,接受到 RST 時蚓胸,會立刻得到一個“connet reset by peer”的異常挣饥。

struct  linger  so_linger;
so_linger.l_onoff = 1;
so_linger.l_linger = 0;
setsockopt(s,SOL_SOCKET,SO_LINGER, &so_linger,sizeof(so_linger));

如果l_onoff為非 0, 且l_linger的值也非 0沛膳,那么調用 close 后扔枫,調用 close 的線程就將阻塞,直到數據被發(fā)送出去锹安,或者設置的l_linger計時時間到短荐。

第二種可能為跨越 TIME_WAIT 狀態(tài)提供了一個可能,不過是一個非常危險的行為叹哭,不值得提倡忍宋。

net.ipv4.tcp_tw_reuse:更安全的設置

那么 Linux 有沒有提供更安全的選擇呢?

當然有风罩。這就是net.ipv4.tcp_tw_reuse選項糠排。

Linux 系統(tǒng)對于net.ipv4.tcp_tw_reuse的解釋如下:

Allow to reuse TIME-WAIT sockets for new connections when it is safe from protocol viewpoint. 
Default value is  0.
It should not be changed without advice/request of technical experts.

這段話的大意是從協議角度理解如果是安全可控的,可以復用處于 TIME_WAIT 的套接字為新的連接所用超升。

那么什么是協議角度理解的安全可控呢入宦?主要有兩點:

  1. 只適用于連接發(fā)起方(C/S 模型中的客戶端);

  2. 對應的 TIME_WAIT 狀態(tài)的連接創(chuàng)建時間超過 1 秒才可以被復用廓俭。

使用這個選項云石,還有一個前提唉工,需要打開對 TCP 時間戳的支持研乒,即net.ipv4.tcp_timestamps=1(默認即為 1)。

要知道淋硝,TCP 協議也在與時俱進雹熬,RFC 1323 中實現了 TCP 拓展規(guī)范宽菜,以便保證 TCP 的高可用,并引入了新的 TCP 選項竿报,兩個 4 字節(jié)的時間戳字段铅乡,用于記錄 TCP 發(fā)送方的當前時間戳和從對端接收到的最新時間戳。由于引入了時間戳烈菌,我們在前面提到的 2MSL 問題就不復存在了阵幸,因為重復的數據包會因為時間戳過期被自然丟棄

總結

在今天的內容里芽世,我講了 TCP 的四次揮手挚赊,重點對 TIME_WAIT 的產生、作用以及優(yōu)化進行了講解济瓢,你需要記住以下三點:

  • TIME_WAIT 的引入是為了讓 TCP 報文得以自然消失荠割,同時為了讓被動關閉方能夠正常關閉

  • 不要試圖使用SO_LINGER設置套接字選項旺矾,跳過 TIME_WAIT蔑鹦;

  • 現代 Linux 系統(tǒng)引入了更安全可控的方案,可以幫助我們盡可能地復用 TIME_WAIT 狀態(tài)的連接箕宙。

思考題

最后按照慣例嚎朽,我留兩道思考題,供你消化今天的內容扒吁。

最大分組 MSL 是 TCP 分組在網絡中存活的最長時間火鼻,你知道這個最長時間是如何達成的?換句話說雕崩,是怎么樣的機制魁索,可以保證在 MSL 達到之后,報文就自然消亡了呢盼铁?
答:記錄一個值粗蔚,比如60s,經過一個網關就減去一定短值饶火,值=0的時候網關決定丟棄鹏控;

RFC 1323 引入了 TCP 時間戳,那么這需要在發(fā)送方和接收方之間定義一個統(tǒng)一的時鐘嗎肤寝?
答:不需要当辐。timestamp不需要交互,只是發(fā)送方使用的鲤看。

當機器出現大量的time wait 狀態(tài)缘揪,原因該如何排查?
答:netstat看一下,看看是什么進程找筝,什么端口蹈垢,為什么會有這個現象。

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末袖裕,一起剝皮案震驚了整個濱河市曹抬,隨后出現的幾起案子,更是在濱河造成了極大的恐慌急鳄,老刑警劉巖谤民,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異疾宏,居然都是意外死亡赖临,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進店門灾锯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來兢榨,“玉大人,你說我怎么就攤上這事顺饮〕炒希” “怎么了?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵兼雄,是天一觀的道長吟逝。 經常有香客問我,道長赦肋,這世上最難降的妖魔是什么块攒? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮佃乘,結果婚禮上囱井,老公的妹妹穿的比我還像新娘趣避。我一直安慰自己庞呕,他們只是感情好,可當我...
    茶點故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布程帕。 她就那樣靜靜地躺著住练,像睡著了一般。 火紅的嫁衣襯著肌膚如雪愁拭。 梳的紋絲不亂的頭發(fā)上讲逛,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天,我揣著相機與錄音岭埠,去河邊找鬼盏混。 笑死顺呕,一個胖子當著我的面吹牛,可吹牛的內容都是我干的括饶。 我是一名探鬼主播,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼来涨,長吁一口氣:“原來是場噩夢啊……” “哼图焰!你這毒婦竟也來了?” 一聲冷哼從身側響起蹦掐,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤技羔,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后卧抗,有當地人在樹林里發(fā)現了一具尸體藤滥,經...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年社裆,在試婚紗的時候發(fā)現自己被綠了拙绊。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,953評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡泳秀,死狀恐怖标沪,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情嗜傅,我是刑警寧澤金句,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站吕嘀,受9級特大地震影響违寞,放射性物質發(fā)生泄漏。R本人自食惡果不足惜偶房,卻給世界環(huán)境...
    茶點故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一趁曼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧棕洋,春花似錦彰阴、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至庆杜,卻和暖如春射众,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背晃财。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工叨橱, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留典蜕,地道東北人。 一個月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓罗洗,卻偏偏與公主長得像愉舔,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子伙菜,可洞房花燭夜當晚...
    茶點故事閱讀 44,901評論 2 355

推薦閱讀更多精彩內容