本文作者張彥飛,原題“聊聊TCP連接耗時(shí)的那些事兒”赡矢,有少許改動(dòng)杭朱。
1、引言
對(duì)于基于互聯(lián)網(wǎng)的通信應(yīng)用(比如IM聊天吹散、推送系統(tǒng))弧械,數(shù)據(jù)傳遞時(shí)使用TCP協(xié)議相對(duì)較多。這是因?yàn)樵赥CP/IP協(xié)議簇的傳輸層協(xié)議中空民,TCP協(xié)議具備可靠的連接刃唐、錯(cuò)誤重傳、擁塞控制等優(yōu)點(diǎn)界轩,所以目前在應(yīng)用場(chǎng)景上比UDP更廣泛一些画饥。
相信你也一定聽(tīng)聞過(guò)TCP也存在一些缺點(diǎn),能常都是老生常談的開(kāi)銷(xiāo)要略大浊猾。但是各路技術(shù)博客里都在單單說(shuō)開(kāi)銷(xiāo)大抖甘、或者開(kāi)銷(xiāo)小,而少見(jiàn)不給出具體的量化分析葫慎。不客氣的講衔彻,類(lèi)似論述都是沒(méi)什么營(yíng)養(yǎng)的廢話(huà)薇宠。
經(jīng)過(guò)日常工作的思考之后,我更想弄明白的是艰额,TCP的開(kāi)銷(xiāo)到底有多大澄港,能否進(jìn)行量化。一條TCP連接的建立需要耗時(shí)延遲多少柄沮,是多少毫秒回梧,還是多少微秒?能不能有一個(gè)哪怕是粗略的量化估計(jì)铡溪?當(dāng)然影響TCP耗時(shí)的因素有很多漂辐,比如網(wǎng)絡(luò)丟包等等。我今天只分享我在工作實(shí)踐中遇到的比較高發(fā)的各種情況棕硫。
寫(xiě)在前面:得益于Linux內(nèi)核的開(kāi)源髓涯,本文中所提及的底層以及具體的內(nèi)核級(jí)代碼例子,都是以L(fǎng)inux系統(tǒng)為例哈扮。
(本文已同步發(fā)布于:http://www.52im.net/thread-3265-1-1.html)
2纬纪、系列文章
本文是系列文章中的第11篇,本系列文章的大綱如下:
《不為人知的網(wǎng)絡(luò)編程(一):淺析TCP協(xié)議中的疑難雜癥(上篇)》
《不為人知的網(wǎng)絡(luò)編程(二):淺析TCP協(xié)議中的疑難雜癥(下篇)》
《不為人知的網(wǎng)絡(luò)編程(三):關(guān)閉TCP連接時(shí)為什么會(huì)TIME_WAIT滑肉、CLOSE_WAIT》
《不為人知的網(wǎng)絡(luò)編程(四):深入研究分析TCP的異常關(guān)閉》
《不為人知的網(wǎng)絡(luò)編程(五):UDP的連接性和負(fù)載均衡》
《不為人知的網(wǎng)絡(luò)編程(六):深入地理解UDP協(xié)議并用好它》
《不為人知的網(wǎng)絡(luò)編程(七):如何讓不可靠的UDP變的可靠包各?》
《不為人知的網(wǎng)絡(luò)編程(八):從數(shù)據(jù)傳輸層深度解密HTTP》
《不為人知的網(wǎng)絡(luò)編程(九):理論聯(lián)系實(shí)際,全方位深入理解DNS》
《不為人知的網(wǎng)絡(luò)編程(十):深入操作系統(tǒng)靶庙,從內(nèi)核理解網(wǎng)絡(luò)包的接收過(guò)程(Linux篇)》
《不為人知的網(wǎng)絡(luò)編程(十一):從底層入手问畅,深度分析TCP連接耗時(shí)的秘密》(本文)
《不為人知的網(wǎng)絡(luò)編程(十二):徹底搞懂TCP協(xié)議層的KeepAlive保活機(jī)制》
《不為人知的網(wǎng)絡(luò)編程(十三):深入操作系統(tǒng)六荒,徹底搞懂127.0.0.1本機(jī)網(wǎng)絡(luò)通信》
《不為人知的網(wǎng)絡(luò)編程(十四):拔掉網(wǎng)線(xiàn)再插上护姆,TCP連接還在嗎?一文即懂掏击!》
3卵皂、理想情況下的TCP連接耗時(shí)分析
要想搞清楚TCP連接的耗時(shí),我們需要詳細(xì)了解連接的建立過(guò)程砚亭。
在前文《深入操作系統(tǒng)灯变,從內(nèi)核理解網(wǎng)絡(luò)包的接收過(guò)程(Linux篇)》中我們介紹了數(shù)據(jù)包在接收端是怎么被接收的:數(shù)據(jù)包從發(fā)送方出來(lái),經(jīng)過(guò)網(wǎng)絡(luò)到達(dá)接收方的網(wǎng)卡捅膘;在接收方網(wǎng)卡將數(shù)據(jù)包DMA到RingBuffer后添祸,內(nèi)核經(jīng)過(guò)硬中斷、軟中斷等機(jī)制來(lái)處理(如果發(fā)送的是用戶(hù)數(shù)據(jù)的話(huà)篓跛,最后會(huì)發(fā)送到socket的接收隊(duì)列中膝捞,并喚醒用戶(hù)進(jìn)程)。
在軟中斷中,當(dāng)一個(gè)包被內(nèi)核從RingBuffer中摘下來(lái)的時(shí)候蔬咬,在內(nèi)核中是用struct sk_buff結(jié)構(gòu)體來(lái)表示的(參見(jiàn)內(nèi)核代碼include/linux/skbuff.h)鲤遥。其中的data成員是接收到的數(shù)據(jù),在協(xié)議棧逐層被處理的時(shí)候林艘,通過(guò)修改指針指向data的不同位置盖奈,來(lái)找到每一層協(xié)議關(guān)心的數(shù)據(jù)。
對(duì)于TCP協(xié)議包來(lái)說(shuō)狐援,它的Header中有一個(gè)重要的字段-flags钢坦。
如下圖:
通過(guò)設(shè)置不同的標(biāo)記位,將TCP包分成SYNC啥酱、FIN爹凹、ACK、RST等類(lèi)型:
1)客戶(hù)端通過(guò)connect系統(tǒng)調(diào)用命令內(nèi)核發(fā)出SYNC镶殷、ACK等包來(lái)實(shí)現(xiàn)和服務(wù)器TCP連接的建立禾酱;
2)在服務(wù)器端,可能會(huì)接收許許多多的連接請(qǐng)求绘趋,內(nèi)核還需要借助一些輔助數(shù)據(jù)結(jié)構(gòu)-半連接隊(duì)列和全連接隊(duì)列颤陶。
我們來(lái)看一下整個(gè)連接過(guò)程:
在這個(gè)連接過(guò)程中,我們來(lái)簡(jiǎn)單分析一下每一步的耗時(shí):
1)客戶(hù)端發(fā)出SYNC包:客戶(hù)端一般是通過(guò)connect系統(tǒng)調(diào)用來(lái)發(fā)出SYN的陷遮,這里牽涉到本機(jī)的系統(tǒng)調(diào)用和軟中斷的CPU耗時(shí)開(kāi)銷(xiāo)滓走;
2)SYN傳到服務(wù)器:SYN從客戶(hù)端網(wǎng)卡被發(fā)出,開(kāi)始“跨過(guò)山和大海帽馋,也穿過(guò)人山人海......”搅方,這是一次長(zhǎng)途遠(yuǎn)距離的網(wǎng)絡(luò)傳輸;
3)服務(wù)器處理SYN包:內(nèi)核通過(guò)軟中斷來(lái)收包绽族,然后放到半連接隊(duì)列中腰懂,然后再發(fā)出SYN/ACK響應(yīng)。又是CPU耗時(shí)開(kāi)銷(xiāo)项秉;
4)SYC/ACK傳到客戶(hù)端:SYC/ACK從服務(wù)器端被發(fā)出后,同樣跨過(guò)很多山慷彤、可能很多大海來(lái)到客戶(hù)端娄蔼。又一次長(zhǎng)途網(wǎng)絡(luò)跋涉;
5)客戶(hù)端處理SYN/ACK:客戶(hù)端內(nèi)核收包并處理SYN后底哗,經(jīng)過(guò)幾u(yù)s的CPU處理岁诉,接著發(fā)出ACK。同樣是軟中斷處理開(kāi)銷(xiāo)跋选;
6)ACK傳到服務(wù)器:和SYN包涕癣,一樣,再經(jīng)過(guò)幾乎同樣遠(yuǎn)的路前标,傳輸一遍坠韩。 又一次長(zhǎng)途網(wǎng)絡(luò)跋涉距潘;
7)服務(wù)端收到ACK:服務(wù)器端內(nèi)核收到并處理ACK,然后把對(duì)應(yīng)的連接從半連接隊(duì)列中取出來(lái)只搁,然后放到全連接隊(duì)列中音比。一次軟中斷CPU開(kāi)銷(xiāo);
8)服務(wù)器端用戶(hù)進(jìn)程喚醒:正在被accpet系統(tǒng)調(diào)用阻塞的用戶(hù)進(jìn)程被喚醒氢惋,然后從全連接隊(duì)列中取出來(lái)已經(jīng)建立好的連接洞翩。一次上下文切換的CPU開(kāi)銷(xiāo)。
以上幾步操作焰望,可以簡(jiǎn)單劃分為兩類(lèi):
第一類(lèi):是內(nèi)核消耗CPU進(jìn)行接收骚亿、發(fā)送或者是處理,包括系統(tǒng)調(diào)用熊赖、軟中斷和上下文切換来屠。它們的耗時(shí)基本都是幾個(gè)us左右;
第二類(lèi):是網(wǎng)絡(luò)傳輸秫舌,當(dāng)包被從一臺(tái)機(jī)器上發(fā)出以后的妖,中間要經(jīng)過(guò)各式各樣的網(wǎng)線(xiàn)、各種交換機(jī)路由器足陨。所以網(wǎng)絡(luò)傳輸?shù)暮臅r(shí)相比本機(jī)的CPU處理嫂粟,就要高的多了。根據(jù)網(wǎng)絡(luò)遠(yuǎn)近一般在幾ms~到幾百ms不等墨缘。
1ms就等于1000us星虹,因此網(wǎng)絡(luò)傳輸耗時(shí)比雙端的CPU開(kāi)銷(xiāo)要高1000倍左右,甚至更高可能還到100000倍镊讼。
所以:在正常的TCP連接的建立過(guò)程中宽涌,一般考慮網(wǎng)絡(luò)延時(shí)即可。
PS:一個(gè)RTT指的是包從一臺(tái)服務(wù)器到另外一臺(tái)服務(wù)器的一個(gè)來(lái)回的延遲時(shí)間蝶棋。
所以從全局來(lái)看:TCP連接建立的網(wǎng)絡(luò)耗時(shí)大約需要三次傳輸卸亮,再加上少許的雙方CPU開(kāi)銷(xiāo),總共大約比1.5倍RTT大一點(diǎn)點(diǎn)玩裙。
不過(guò)兼贸,從客戶(hù)端視角來(lái)看:只要ACK包發(fā)出了,內(nèi)核就認(rèn)為連接是建立成功了吃溅。所以如果在客戶(hù)端打點(diǎn)統(tǒng)計(jì)TCP連接建立耗時(shí)的話(huà)溶诞,只需要兩次傳輸耗時(shí)-既1個(gè)RTT多一點(diǎn)的時(shí)間。(對(duì)于服務(wù)器端視角來(lái)看同理决侈,從SYN包收到開(kāi)始算螺垢,到收到ACK,中間也是一次RTT耗時(shí))。
4枉圃、極端情況下的TCP連接耗時(shí)分析
上一節(jié)可以看到:在客戶(hù)端視角功茴,正常情況下一次TCP連接總的耗時(shí)也就就大約是一次網(wǎng)絡(luò)RTT的耗時(shí)。如果所有的事情都這么簡(jiǎn)單讯蒲,我想我的這次分享也就沒(méi)有必要了痊土。事情不一定總是這么美好,意外的發(fā)生在所難免墨林。
在某些情況下蔫浆,可能會(huì)導(dǎo)致TCP連接時(shí)的網(wǎng)絡(luò)傳輸耗時(shí)上漲辅愿、CPU處理開(kāi)銷(xiāo)增加蔫饰、甚至是連接失敗疗锐。本節(jié)將就我在線(xiàn)上遇到過(guò)的各種切身體會(huì)的溝溝坎坎,來(lái)分析一下極端情況下的TCP連接耗時(shí)情況搔耕。
4.1 客戶(hù)端connect調(diào)用耗時(shí)失控案例
正常一個(gè)系統(tǒng)調(diào)用的耗時(shí)也就是幾個(gè)us(微秒)左右隙袁。但是在我的《追蹤將服務(wù)器CPU耗光的兇手!》一文中,筆者的一臺(tái)服務(wù)器當(dāng)時(shí)遇到一個(gè)狀況:某次運(yùn)維同學(xué)轉(zhuǎn)達(dá)過(guò)來(lái)說(shuō)該服務(wù)CPU不夠用了弃榨,需要擴(kuò)容菩收。
當(dāng)時(shí)的服務(wù)器監(jiān)控如下圖:
該服務(wù)之前一直每秒抗2000左右的qps,CPU的idel一直有70%+鲸睛,怎么突然就CPU一下就不夠用了呢娜饵。
而且更奇怪的是CPU被打到谷底的那一段時(shí)間,負(fù)載卻并不高(服務(wù)器為4核機(jī)器官辈,負(fù)載3-4是比較正常的)箱舞。
后來(lái)經(jīng)過(guò)排查以后發(fā)現(xiàn)當(dāng)TCP客戶(hù)端TIME_WAIT有30000左右,導(dǎo)致可用端口不是特別充足的時(shí)候拳亿,connect系統(tǒng)調(diào)用的CPU開(kāi)銷(xiāo)直接上漲了100多倍晴股,每次耗時(shí)達(dá)到了2500us(微秒),達(dá)到了毫秒級(jí)別肺魁。
當(dāng)遇到這種問(wèn)題的時(shí)候电湘,雖然TCP連接建立耗時(shí)只增加了2ms左右,整體TCP連接耗時(shí)看起來(lái)還可接受鹅经。但這里的問(wèn)題在于這2ms多都是在消耗CPU的周期胡桨,所以問(wèn)題不小。
解決起來(lái)也非常簡(jiǎn)單瞬雹,辦法很多:修改內(nèi)核參數(shù)net.ipv4.ip_local_port_range多預(yù)留一些端口號(hào)、改用長(zhǎng)連接都可以刽虹。
4.2 TCP半/全連接隊(duì)列滿(mǎn)的案例
如果連接建立的過(guò)程中酗捌,任意一個(gè)隊(duì)列滿(mǎn)了,那么客戶(hù)端發(fā)送過(guò)來(lái)的syn或者ack就會(huì)被丟棄∨昼停客戶(hù)端等待很長(zhǎng)一段時(shí)間無(wú)果后尚镰,然后會(huì)發(fā)出TCP Retransmission重傳。
拿半連接隊(duì)列舉例:
要知道的是上面TCP握手超時(shí)重傳的時(shí)間是秒級(jí)別的哪廓。也就是說(shuō)一旦server端的連接隊(duì)列導(dǎo)致連接建立不成功狗唉,那么光建立連接就至少需要秒級(jí)以上。而正常的在同機(jī)房的情況下只是不到1毫秒的事情涡真,整整高了1000倍左右分俯。
尤其是對(duì)于給用戶(hù)提供實(shí)時(shí)服務(wù)的程序來(lái)說(shuō),用戶(hù)體驗(yàn)將會(huì)受到較大影響哆料。如果連重傳也沒(méi)有握手成功的話(huà)缸剪,很可能等不及二次重試,這個(gè)用戶(hù)訪(fǎng)問(wèn)直接就超時(shí)了东亦。
還有另外一個(gè)更壞的情況是:它還有可能會(huì)影響其它的用戶(hù)杏节。
假如你使用的是進(jìn)程/線(xiàn)程池這種模型提供服務(wù),比如:php-fpm典阵。我們知道fpm進(jìn)程是阻塞的奋渔,當(dāng)它響應(yīng)一個(gè)用戶(hù)請(qǐng)求的時(shí)候,該進(jìn)程是沒(méi)有辦法再響應(yīng)其它請(qǐng)求的壮啊。假如你開(kāi)了100個(gè)進(jìn)程/線(xiàn)程嫉鲸,而某一段時(shí)間內(nèi)有50個(gè)進(jìn)程/線(xiàn)程卡在和redis或者mysql服務(wù)器的握手連接上了(注意:這個(gè)時(shí)候你的服務(wù)器是TCP連接的客戶(hù)端一方)。這一段時(shí)間內(nèi)相當(dāng)于你可以用的正常工作的進(jìn)程/線(xiàn)程只有50個(gè)了他巨。而這個(gè)50個(gè)worker可能根本處理不過(guò)來(lái)充坑,這時(shí)候你的服務(wù)可能就會(huì)產(chǎn)生擁堵。再持續(xù)稍微時(shí)間長(zhǎng)一點(diǎn)的話(huà)染突,可能就產(chǎn)生雪崩了捻爷,整個(gè)服務(wù)都有可能會(huì)受影響。
既然后果有可能這么嚴(yán)重份企,那么我們?nèi)绾尾榭次覀兪诸^的服務(wù)是否有因?yàn)榘?全連接隊(duì)列滿(mǎn)的情況發(fā)生呢也榄?
在客戶(hù)端:可以抓包查看是否有SYN的TCP Retransmission。如果有偶發(fā)的TCP Retransmission司志,那就說(shuō)明對(duì)應(yīng)的服務(wù)端連接隊(duì)列可能有問(wèn)題了甜紫。
在服務(wù)端的話(huà):查看起來(lái)就更方便一些了。netstat -s?可查看到當(dāng)前系統(tǒng)半連接隊(duì)列滿(mǎn)導(dǎo)致的丟包統(tǒng)計(jì)骂远,但該數(shù)字記錄的是總丟包數(shù)囚霸。你需要再借助?watch?命令動(dòng)態(tài)監(jiān)控。如果下面的數(shù)字在你監(jiān)控的過(guò)程中變了激才,那說(shuō)明當(dāng)前服務(wù)器有因?yàn)榘脒B接隊(duì)列滿(mǎn)而產(chǎn)生的丟包拓型。你可能需要加大你的半連接隊(duì)列的長(zhǎng)度了额嘿。
$ watch'netstat -s | grep LISTEN'
????8 SYNs to LISTEN sockets ignored
對(duì)于全連接隊(duì)列來(lái)說(shuō)呢,查看方法也類(lèi)似:
$ watch'netstat -s? | grep overflowed'
????160 timesthe listen queue of a socket overflowed
如果你的服務(wù)因?yàn)殛?duì)列滿(mǎn)產(chǎn)生丟包劣挫,其中一個(gè)做法就是加大半/全連接隊(duì)列的長(zhǎng)度册养。 半連接隊(duì)列長(zhǎng)度Linux內(nèi)核中,主要受tcp_max_syn_backlog影響 加大它到一個(gè)合適的值就可以压固。
# cat /proc/sys/net/ipv4/tcp_max_syn_backlog
1024
# echo "2048" > /proc/sys/net/ipv4/tcp_max_syn_backlog
全連接隊(duì)列長(zhǎng)度是應(yīng)用程序調(diào)用listen時(shí)傳入的backlog以及內(nèi)核參數(shù)net.core.somaxconn二者之中較小的那個(gè)球拦。你可能需要同時(shí)調(diào)整你的應(yīng)用程序和該內(nèi)核參數(shù)。
# cat /proc/sys/net/core/somaxconn
128
# echo "256" > /proc/sys/net/core/somaxconn
改完之后我們可以通過(guò)ss命令輸出的Send-Q確認(rèn)最終生效長(zhǎng)度:
$ ss -nlt
Recv-Q Send-Q Local Address:Port Address:Port
0????? 128??? *:80?????????????? *:*
Recv-Q告訴了我們當(dāng)前該進(jìn)程的全連接隊(duì)列使用長(zhǎng)度情況帐我。如果Recv-Q已經(jīng)逼近了Send-Q,那么可能不需要等到丟包也應(yīng)該準(zhǔn)備加大你的全連接隊(duì)列了坎炼。
如果加大隊(duì)列后仍然有非常偶發(fā)的隊(duì)列溢出的話(huà),我們可以暫且容忍焚刚。
如果仍然有較長(zhǎng)時(shí)間處理不過(guò)來(lái)怎么辦点弯?
另外一個(gè)做法就是直接報(bào)錯(cuò),不要讓客戶(hù)端超時(shí)等待矿咕。
例如將Redis抢肛、Mysql等后端接口的內(nèi)核參數(shù)tcp_abort_on_overflow為1。如果隊(duì)列滿(mǎn)了碳柱,直接發(fā)reset給client捡絮。告訴后端進(jìn)程/線(xiàn)程不要癡情地傻等。這時(shí)候client會(huì)收到錯(cuò)誤“connection reset by peer”莲镣。犧牲一個(gè)用戶(hù)的訪(fǎng)問(wèn)請(qǐng)求福稳,要比把整個(gè)站都搞崩了還是要強(qiáng)的。
5瑞侮、TCP連接耗時(shí)實(shí)測(cè)分析
5.1 測(cè)試前的準(zhǔn)備
我寫(xiě)了一段非常簡(jiǎn)單的代碼的圆,用來(lái)在客戶(hù)端統(tǒng)計(jì)每創(chuàng)建一個(gè)TCP連接需要消耗多長(zhǎng)時(shí)間。
<?php
$ip= {服務(wù)器ip};
$port= {服務(wù)器端口};
$count= 50000;
function buildConnect($ip,$port,$num){
????for($i=0;$i<$num;$i++){
????????$socket= socket_create(AF_INET,SOCK_STREAM,SOL_TCP);
????????if($socket==false) {
????????????echo"$ip $port socket_create() 失敗的原因是:".socket_strerror(socket_last_error($socket))."\n";
????????????sleep(5);
????????????continue;
????????}
????????if(false == socket_connect($socket, $ip, $port)){
????????????echo"$ip $port socket_connect() 失敗的原因是:".socket_strerror(socket_last_error($socket))."\n";
????????????sleep(5);
????????????continue;
????????}
????????socket_close($socket);
????}
}
$t1= microtime(true);
buildConnect($ip, $port, $count);
echo(($t2-$t1)*1000).'ms';
在測(cè)試之前半火,我們需要本機(jī)linux可用的端口數(shù)充足越妈,如果不夠50000個(gè),最好調(diào)整充足钮糖。
# echo "5000?? 65000" /proc/sys/net/ipv4/ip_local_port_range
5.2 正常情況下的測(cè)試
注意:無(wú)論是客戶(hù)端還是服務(wù)器端都不要選擇有線(xiàn)上服務(wù)在跑的機(jī)器梅掠,否則你的測(cè)試可能會(huì)影響正常用戶(hù)訪(fǎng)問(wèn)
首先:我的客戶(hù)端位于河北懷來(lái)的IDC機(jī)房?jī)?nèi),服務(wù)器選擇的是公司廣東機(jī)房的某臺(tái)機(jī)器店归。執(zhí)行ping命令得到的延遲大約是37ms阎抒,使用上述腳本建立50000次連接后,得到的連接平均耗時(shí)也是37ms消痛。
這是因?yàn)榍懊嫖覀冋f(shuō)過(guò)的且叁,對(duì)于客戶(hù)端來(lái)看,第三次的握手只要包發(fā)送出去秩伞,就認(rèn)為是握手成功了逞带,所以只需要一次RTT质涛、兩次傳輸耗時(shí)。雖然這中間還會(huì)有客戶(hù)端和服務(wù)端的系統(tǒng)調(diào)用開(kāi)銷(xiāo)掰担、軟中斷開(kāi)銷(xiāo),但由于它們的開(kāi)銷(xiāo)正常情況下只有幾個(gè)us(微秒)怒炸,所以對(duì)總的連接建立延時(shí)影響不大带饱。
接下來(lái):我換了一臺(tái)目標(biāo)服務(wù)器,該服務(wù)器所在機(jī)房位于北京阅羹。離懷來(lái)有一些距離勺疼,但是和廣東比起來(lái)可要近多了。這一次ping出來(lái)的RTT是1.6~1.7ms左右捏鱼,在客戶(hù)端統(tǒng)計(jì)建立50000次連接后算出每條連接耗時(shí)是1.64ms执庐。
再做一次實(shí)驗(yàn):這次選中實(shí)驗(yàn)的服務(wù)器和客戶(hù)端直接位于同一個(gè)機(jī)房?jī)?nèi),ping延遲在0.2ms~0.3ms左右导梆。跑了以上腳本以后轨淌,實(shí)驗(yàn)結(jié)果是50000 TCP連接總共消耗了11605ms,平均每次需要0.23ms看尼。
線(xiàn)上架構(gòu)提示:這里看到同機(jī)房延遲只有零點(diǎn)幾ms递鹉,但是跨個(gè)距離不遠(yuǎn)的機(jī)房,光TCP握手耗時(shí)就漲了4倍藏斩。如果再要是跨地區(qū)到廣東躏结,那就是百倍的耗時(shí)差距了。線(xiàn)上部署時(shí)狰域,理想的方案是將自己服務(wù)依賴(lài)的各種mysql媳拴、redis等服務(wù)和自己部署在同一個(gè)地區(qū)、同一個(gè)機(jī)房(再變態(tài)一點(diǎn)兆览,甚至可以是甚至是同一個(gè)機(jī)架)屈溉。因?yàn)檫@樣包括TCP鏈接建立啥的各種網(wǎng)絡(luò)包傳輸都要快很多。要盡可能避免長(zhǎng)途跨地區(qū)機(jī)房的調(diào)用情況出現(xiàn)拓颓。
5.3 TCP連接隊(duì)列溢出情況下的測(cè)試
測(cè)試完了跨地區(qū)语婴、跨機(jī)房和跨機(jī)器。這次為了快驶睦,直接和本機(jī)建立連接結(jié)果會(huì)咋樣呢砰左?
Ping本機(jī)ip或127.0.0.1的延遲大概是0.02ms,本機(jī)ip比其它機(jī)器RTT肯定要短场航。我覺(jué)得肯定連接會(huì)非巢迹快,嗯實(shí)驗(yàn)一下溉痢。
連續(xù)建立5W TCP連接:總時(shí)間消耗27154ms僻造,平均每次需要0.54ms左右憋他。
嗯!髓削?怎么比跨機(jī)器還長(zhǎng)很多竹挡?
有了前面的理論基礎(chǔ),我們應(yīng)該想到了:由于本機(jī)RTT太短立膛,所以瞬間連接建立請(qǐng)求量很大揪罕,就會(huì)導(dǎo)致全連接隊(duì)列或者半連接隊(duì)列被打滿(mǎn)的情況。一旦發(fā)生隊(duì)列滿(mǎn)宝泵,當(dāng)時(shí)撞上的那個(gè)連接請(qǐng)求就得需要3秒+的連接建立延時(shí)好啰。所以上面的實(shí)驗(yàn)結(jié)果中,平均耗時(shí)看起來(lái)比RTT高很多儿奶。
在實(shí)驗(yàn)的過(guò)程中框往,我使用tcpdump抓包看到了下面的一幕。原來(lái)有少部分握手耗時(shí)3s+闯捎,原因是半連接隊(duì)列滿(mǎn)了導(dǎo)致客戶(hù)端等待超時(shí)后進(jìn)行了SYN的重傳椰弊。
我們又重新改成每500個(gè)連接,sleep 1秒隙券。嗯好男应,終于沒(méi)有卡的了(或者也可以加大連接隊(duì)列長(zhǎng)度)。
結(jié)論是:本機(jī)50000次TCP連接在客戶(hù)端統(tǒng)計(jì)總耗時(shí)102399 ms娱仔,減去sleep的100秒后沐飘,平均每個(gè)TCP連接消耗0.048ms。比ping延遲略高一些牲迫。
這是因?yàn)楫?dāng)RTT變的足夠小的時(shí)候耐朴,內(nèi)核CPU耗時(shí)開(kāi)銷(xiāo)就會(huì)顯現(xiàn)出來(lái)了,另外TCP連接要比ping的icmp協(xié)議更復(fù)雜一些盹憎,所以比ping延遲略高0.02ms左右比較正常筛峭。
6、本文小結(jié)
TCP連接在建立異常的情況下陪每,可能需要好幾秒影晓,一個(gè)壞處就是會(huì)影響用戶(hù)體驗(yàn),甚至導(dǎo)致當(dāng)前用戶(hù)訪(fǎng)問(wèn)超時(shí)都有可能檩禾。另外一個(gè)壞處是可能會(huì)誘發(fā)雪崩挂签。
所以當(dāng)你的服務(wù)器使用短連接的方式訪(fǎng)問(wèn)數(shù)據(jù)的時(shí)候:一定要學(xué)會(huì)要監(jiān)控你的服務(wù)器的連接建立是否有異常狀態(tài)發(fā)生。如果有盼产,學(xué)會(huì)優(yōu)化掉它饵婆。當(dāng)然你也可以采用本機(jī)內(nèi)存緩存,或者使用連接池來(lái)保持長(zhǎng)連接戏售,通過(guò)這兩種方式直接避免掉TCP握手揮手的各種開(kāi)銷(xiāo)也可以侨核。
再說(shuō)正常情況下:TCP建立的延時(shí)大約就是兩臺(tái)機(jī)器之間的一個(gè)RTT耗時(shí)草穆,這是避免不了的。但是你可以控制兩臺(tái)機(jī)器之間的物理距離來(lái)降低這個(gè)RTT搓译,比如把你要訪(fǎng)問(wèn)的redis盡可能地部署的離后端接口機(jī)器近一點(diǎn)悲柱,這樣RTT也能從幾十ms削減到最低可能零點(diǎn)幾ms。
最后我們?cè)偎伎家幌拢?/b>如果我們把服務(wù)器部署在北京些己,給紐約的用戶(hù)訪(fǎng)問(wèn)可行嗎诗祸?
前面的我們同機(jī)房也好,跨機(jī)房也好轴总,電信號(hào)傳輸?shù)暮臅r(shí)基本可以忽略(因?yàn)槲锢砭嚯x很近),網(wǎng)絡(luò)延遲基本上是轉(zhuǎn)發(fā)設(shè)備占用的耗時(shí)博个。但是如果是跨越了半個(gè)地球的話(huà)怀樟,電信號(hào)的傳輸耗時(shí)我們可得算一算了。 北京到紐約的球面距離大概是15000公里盆佣,那么拋開(kāi)設(shè)備轉(zhuǎn)發(fā)延遲往堡,僅僅光速傳播一個(gè)來(lái)回(RTT是Rround trip time,要跑兩次)共耍,需要時(shí)間 = 15,000,000 *2 / 光速 = 100ms虑灰。實(shí)際的延遲可能比這個(gè)還要大一些,一般都得200ms以上痹兜。建立在這個(gè)延遲上穆咐,要想提供用戶(hù)能訪(fǎng)問(wèn)的秒級(jí)服務(wù)就很困難了。所以對(duì)于海外用戶(hù)字旭,最好都要在當(dāng)?shù)亟C(jī)房或者購(gòu)買(mǎi)海外的服務(wù)器对湃。
附錄:更多網(wǎng)絡(luò)編程精華資料
[1] 網(wǎng)絡(luò)編程(基礎(chǔ))資料:
《TCP/IP詳解?-?第11章·UDP:用戶(hù)數(shù)據(jù)報(bào)協(xié)議》
《TCP/IP詳解?-?第17章·TCP:傳輸控制協(xié)議》
《TCP/IP詳解?-?第21章·TCP的超時(shí)與重傳》
《技術(shù)往事:改變世界的TCP/IP協(xié)議(珍貴多圖、手機(jī)慎點(diǎn))》
《通俗易懂-深入理解TCP協(xié)議(上):理論基礎(chǔ)》
《通俗易懂-深入理解TCP協(xié)議(下):RTT遗淳、滑動(dòng)窗口拍柒、擁塞處理》
《理論經(jīng)典:TCP協(xié)議的3次握手與4次揮手過(guò)程詳解》
《理論聯(lián)系實(shí)際:Wireshark抓包分析TCP 3次握手、4次揮手過(guò)程》
《計(jì)算機(jī)網(wǎng)絡(luò)通訊協(xié)議關(guān)系圖(中文珍藏版)》
《P2P技術(shù)詳解(一):NAT詳解——詳細(xì)原理拆讯、P2P簡(jiǎn)介》
《P2P技術(shù)詳解(二):P2P中的NAT穿越(打洞)方案詳解(基本原理篇)》
《P2P技術(shù)詳解(三):P2P中的NAT穿越(打洞)方案詳解(進(jìn)階分析篇)》
《P2P技術(shù)詳解(四):P2P技術(shù)之STUN、TURN养叛、ICE詳解》
《通俗易懂:快速理解P2P技術(shù)中的NAT穿透原理》
《Java的BIO和NIO很難懂种呐?用代碼實(shí)踐給你看,再不懂我轉(zhuǎn)行一铅!》
《網(wǎng)絡(luò)編程懶人入門(mén)(一):快速理解網(wǎng)絡(luò)通信協(xié)議(上篇)》
《網(wǎng)絡(luò)編程懶人入門(mén)(二):快速理解網(wǎng)絡(luò)通信協(xié)議(下篇)》
《網(wǎng)絡(luò)編程懶人入門(mén)(三):快速理解TCP協(xié)議一篇就夠》
《網(wǎng)絡(luò)編程懶人入門(mén)(四):快速理解TCP和UDP的差異》
《網(wǎng)絡(luò)編程懶人入門(mén)(五):快速理解為什么說(shuō)UDP有時(shí)比TCP更有優(yōu)勢(shì)》
《網(wǎng)絡(luò)編程懶人入門(mén)(六):史上最通俗的集線(xiàn)器陕贮、交換機(jī)、路由器功能原理入門(mén)》
《網(wǎng)絡(luò)編程懶人入門(mén)(七):深入淺出潘飘,全面理解HTTP協(xié)議》
《網(wǎng)絡(luò)編程懶人入門(mén)(八):手把手教你寫(xiě)基于TCP的Socket長(zhǎng)連接》
《網(wǎng)絡(luò)編程懶人入門(mén)(九):通俗講解肮之,有了IP地址掉缺,為何還要用MAC地址?》
《網(wǎng)絡(luò)編程懶人入門(mén)(十):一泡尿的時(shí)間戈擒,快速讀懂QUIC協(xié)議》
《網(wǎng)絡(luò)編程懶人入門(mén)(十一):一文讀懂什么是IPv6》
《網(wǎng)絡(luò)編程懶人入門(mén)(十二):快速讀懂Http/3協(xié)議眶明,一篇就夠!》
《網(wǎng)絡(luò)編程懶人入門(mén)(十三):一泡尿的時(shí)間筐高,快速搞懂TCP和UDP的區(qū)別》
《網(wǎng)絡(luò)編程懶人入門(mén)(十四):到底什么是Socket搜囱?一文即懂!》
《技術(shù)掃盲:新一代基于UDP的低延時(shí)網(wǎng)絡(luò)傳輸層協(xié)議——QUIC詳解》
《讓互聯(lián)網(wǎng)更快:新一代QUIC協(xié)議在騰訊的技術(shù)實(shí)踐分享》
《聊聊iOS中網(wǎng)絡(luò)編程長(zhǎng)連接的那些事》
《IPv6技術(shù)詳解:基本概念柑土、應(yīng)用現(xiàn)狀蜀肘、技術(shù)實(shí)踐(上篇)》
《IPv6技術(shù)詳解:基本概念、應(yīng)用現(xiàn)狀稽屏、技術(shù)實(shí)踐(下篇)》
《Java對(duì)IPv6的支持詳解:支持情況扮宠、相關(guān)API、演示代碼》
《從HTTP/0.9到HTTP/2:一文讀懂HTTP協(xié)議的歷史演變和設(shè)計(jì)思路》
《腦殘式網(wǎng)絡(luò)編程入門(mén)(一):跟著動(dòng)畫(huà)來(lái)學(xué)TCP三次握手和四次揮手》
《腦殘式網(wǎng)絡(luò)編程入門(mén)(二):我們?cè)谧x寫(xiě)Socket時(shí)狐榔,究竟在讀寫(xiě)什么坛增?》
《腦殘式網(wǎng)絡(luò)編程入門(mén)(三):HTTP協(xié)議必知必會(huì)的一些知識(shí)》
《腦殘式網(wǎng)絡(luò)編程入門(mén)(四):快速理解HTTP/2的服務(wù)器推送(Server Push)》
《腦殘式網(wǎng)絡(luò)編程入門(mén)(五):每天都在用的Ping命令,它到底是什么薄腻?》
《腦殘式網(wǎng)絡(luò)編程入門(mén)(六):什么是公網(wǎng)IP和內(nèi)網(wǎng)IP收捣?NAT轉(zhuǎn)換又是什么鬼?》
《腦殘式網(wǎng)絡(luò)編程入門(mén)(七):面視必備庵楷,史上最通俗計(jì)算機(jī)網(wǎng)絡(luò)分層詳解》
《腦殘式網(wǎng)絡(luò)編程入門(mén)(八):你真的了解127.0.0.1和0.0.0.0的區(qū)別罢艾?》
《腦殘式網(wǎng)絡(luò)編程入門(mén)(九):面試必考,史上最通俗大小端字節(jié)序詳解》
《邁向高階:優(yōu)秀Android程序員必知必會(huì)的網(wǎng)絡(luò)基礎(chǔ)》
《Android程序員必知必會(huì)的網(wǎng)絡(luò)通信傳輸層協(xié)議——UDP和TCP》
《技術(shù)大牛陳碩的分享:由淺入深尽纽,網(wǎng)絡(luò)編程學(xué)習(xí)經(jīng)驗(yàn)干貨總結(jié)》
《可能會(huì)搞砸你的面試:你知道一個(gè)TCP連接上能發(fā)起多少個(gè)HTTP請(qǐng)求嗎昆婿?》
[2] 網(wǎng)絡(luò)編程(高階)資料:
《高性能網(wǎng)絡(luò)編程(一):?jiǎn)闻_(tái)服務(wù)器并發(fā)TCP連接數(shù)到底可以有多少》
《高性能網(wǎng)絡(luò)編程(二):上一個(gè)10年,著名的C10K并發(fā)連接問(wèn)題》
《高性能網(wǎng)絡(luò)編程(三):下一個(gè)10年挎春,是時(shí)候考慮C10M并發(fā)問(wèn)題了》
《高性能網(wǎng)絡(luò)編程(四):從C10K到C10M高性能網(wǎng)絡(luò)應(yīng)用的理論探索》
《高性能網(wǎng)絡(luò)編程(五):一文讀懂高性能網(wǎng)絡(luò)編程中的I/O模型》
《高性能網(wǎng)絡(luò)編程(六):一文讀懂高性能網(wǎng)絡(luò)編程中的線(xiàn)程模型》
《高性能網(wǎng)絡(luò)編程(七):到底什么是高并發(fā)看疙?一文即懂!》
《IM開(kāi)發(fā)者的零基礎(chǔ)通信技術(shù)入門(mén)(一):通信交換技術(shù)的百年發(fā)展史(上)》
《IM開(kāi)發(fā)者的零基礎(chǔ)通信技術(shù)入門(mén)(二):通信交換技術(shù)的百年發(fā)展史(下)》
《IM開(kāi)發(fā)者的零基礎(chǔ)通信技術(shù)入門(mén)(三):國(guó)人通信方式的百年變遷》
《IM開(kāi)發(fā)者的零基礎(chǔ)通信技術(shù)入門(mén)(四):手機(jī)的演進(jìn)直奋,史上最全移動(dòng)終端發(fā)展史》
《IM開(kāi)發(fā)者的零基礎(chǔ)通信技術(shù)入門(mén)(五):1G到5G能庆,30年移動(dòng)通信技術(shù)演進(jìn)史》
《IM開(kāi)發(fā)者的零基礎(chǔ)通信技術(shù)入門(mén)(六):移動(dòng)終端的接頭人——“基站”技術(shù)》
《IM開(kāi)發(fā)者的零基礎(chǔ)通信技術(shù)入門(mén)(七):移動(dòng)終端的千里馬——“電磁波”》
《IM開(kāi)發(fā)者的零基礎(chǔ)通信技術(shù)入門(mén)(八):零基礎(chǔ),史上最強(qiáng)“天線(xiàn)”原理掃盲》
《IM開(kāi)發(fā)者的零基礎(chǔ)通信技術(shù)入門(mén)(九):無(wú)線(xiàn)通信網(wǎng)絡(luò)的中樞——“核心網(wǎng)”》
《IM開(kāi)發(fā)者的零基礎(chǔ)通信技術(shù)入門(mén)(十):零基礎(chǔ)脚线,史上最強(qiáng)5G技術(shù)掃盲》
《IM開(kāi)發(fā)者的零基礎(chǔ)通信技術(shù)入門(mén)(十一):為什么WiFi信號(hào)差搁胆?一文即懂!》
《IM開(kāi)發(fā)者的零基礎(chǔ)通信技術(shù)入門(mén)(十二):上網(wǎng)卡頓?網(wǎng)絡(luò)掉線(xiàn)渠旁?一文即懂攀例!》
《IM開(kāi)發(fā)者的零基礎(chǔ)通信技術(shù)入門(mén)(十三):為什么手機(jī)信號(hào)差?一文即懂顾腊!》
《IM開(kāi)發(fā)者的零基礎(chǔ)通信技術(shù)入門(mén)(十四):高鐵上無(wú)線(xiàn)上網(wǎng)有多難粤铭?一文即懂!》
《IM開(kāi)發(fā)者的零基礎(chǔ)通信技術(shù)入門(mén)(十五):理解定位技術(shù)杂靶,一篇就夠》
《以網(wǎng)游服務(wù)端的網(wǎng)絡(luò)接入層設(shè)計(jì)為例梆惯,理解實(shí)時(shí)通信的技術(shù)挑戰(zhàn)》
《知乎技術(shù)分享:知乎千萬(wàn)級(jí)并發(fā)的高性能長(zhǎng)連接網(wǎng)關(guān)技術(shù)實(shí)踐》
《淘寶技術(shù)分享:手淘?xún)|級(jí)移動(dòng)端接入層網(wǎng)關(guān)的技術(shù)演進(jìn)之路》
(本文已同步發(fā)布于:http://www.52im.net/thread-3265-1-1.html)