TCP 三次握手原理,你真的理解嗎?
問(wèn)題描述
場(chǎng)景:JAVA的client和server,使用socket通信。server使用NIO。
1.間歇性得出現(xiàn)client向server建立連接三次握手已經(jīng)完成,但server的selector沒(méi)有響應(yīng)到這連接。
2.出問(wèn)題的時(shí)間點(diǎn),會(huì)同時(shí)有很多連接出現(xiàn)這個(gè)問(wèn)題。
3.selector沒(méi)有銷毀重建,一直用的都是一個(gè)。
4.程序剛啟動(dòng)的時(shí)候必會(huì)出現(xiàn)一些,之后會(huì)間歇性出現(xiàn)。
分析問(wèn)題
正常TCP建連接三次握手過(guò)程:
- 第一步:client 發(fā)送 syn 到server 發(fā)起握手;
- 第二步:server 收到 syn后回復(fù)syn+ack給client;
- 第三步:client 收到syn+ack后,回復(fù)server一個(gè)ack表示收到了server的syn+ack(此時(shí)client的56911端口的連接已經(jīng)是established)。
從問(wèn)題的描述來(lái)看,有點(diǎn)像TCP建連接的時(shí)候全連接隊(duì)列(accept隊(duì)列,后面具體講)滿了,尤其是癥狀2、4. 為了證明是這個(gè)原因,馬上通過(guò) netstat -s | egrep "listen" 去看隊(duì)列的溢出統(tǒng)計(jì)數(shù)據(jù):
反復(fù)看了幾次之后發(fā)現(xiàn)這個(gè)overflowed 一直在增加,那么可以明確的是server上全連接隊(duì)列一定溢出了。
接著查看溢出后,OS怎么處理:
tcp_abort_on_overflow 為0表示如果三次握手第三步的時(shí)候全連接隊(duì)列滿了那么server扔掉client 發(fā)過(guò)來(lái)的ack(在server端認(rèn)為連接還沒(méi)建立起來(lái))
為了證明客戶端應(yīng)用代碼的異常跟全連接隊(duì)列滿有關(guān)系,我先把tcp_abort_on_overflow修改成 1,1表示第三步的時(shí)候如果全連接隊(duì)列滿了,server發(fā)送一個(gè)reset包給client,表示廢掉這個(gè)握手過(guò)程和這個(gè)連接(本來(lái)在server端這個(gè)連接就還沒(méi)建立起來(lái))。
接著測(cè)試,這時(shí)在客戶端異常中可以看到很多connection reset by peer的錯(cuò)誤,到此證明客戶端錯(cuò)誤是這個(gè)原因?qū)е碌模ㄟ壿媷?yán)謹(jǐn)、快速證明問(wèn)題的關(guān)鍵點(diǎn)所在)。
于是開發(fā)同學(xué)翻看java 源代碼發(fā)現(xiàn)socket 默認(rèn)的backlog(這個(gè)值控制全連接隊(duì)列的大小,后面再詳述)是50,于是改大重新跑,經(jīng)過(guò)12個(gè)小時(shí)以上的壓測(cè),這個(gè)錯(cuò)誤一次都沒(méi)出現(xiàn)了,同時(shí)觀察到 overflowed 也不再增加了。
到此問(wèn)題解決,簡(jiǎn)單來(lái)說(shuō)TCP三次握手后有個(gè)accept隊(duì)列,進(jìn)到這個(gè)隊(duì)列才能從Listen變成accept,默認(rèn)backlog 值是50,很容易就滿了。滿了之后握手第三步的時(shí)候server就忽略了client發(fā)過(guò)來(lái)的ack包(隔一段時(shí)間server重發(fā)握手第二步的syn+ack包給client),如果這個(gè)連接一直排不上隊(duì)就異常了。
但是不能只是滿足問(wèn)題的解決,而是要去復(fù)盤解決過(guò)程,中間涉及到了哪些知識(shí)點(diǎn)是我所缺失或者理解不到位的;這個(gè)問(wèn)題除了上面的異常信息表現(xiàn)出來(lái)之外,還有沒(méi)有更明確地指征來(lái)查看和確認(rèn)這個(gè)問(wèn)題。
深入理解TCP握手過(guò)程中建連接的流程和隊(duì)列
(圖片來(lái)源:http://www.cnxct.com/something-about-phpfpm-s-backlog/)
如上圖所示,這里有兩個(gè)隊(duì)列:syns queue(半連接隊(duì)列);accept queue(全連接隊(duì)列)。
三次握手中,在第一步server收到client的syn后,把這個(gè)連接信息放到半連接隊(duì)列中,同時(shí)回復(fù)syn+ack給client(第二步);
第三步的時(shí)候server收到client的ack,如果這時(shí)全連接隊(duì)列沒(méi)滿,那么從半連接隊(duì)列拿出這個(gè)連接的信息放入到全連接隊(duì)列中,否則按tcp_abort_on_overflow指示的執(zhí)行。
這時(shí)如果全連接隊(duì)列滿了并且tcp_abort_on_overflow是0的話,server過(guò)一段時(shí)間再次發(fā)送syn+ack給client(也就是重新走握手的第二步),如果client超時(shí)等待比較短,client就很容易異常了。
在我們的os中retry 第二步的默認(rèn)次數(shù)是2(centos默認(rèn)是5次):
如果TCP連接隊(duì)列溢出,有哪些指標(biāo)可以看呢?
上述解決過(guò)程有點(diǎn)繞,聽起來(lái)懵,那么下次再出現(xiàn)類似問(wèn)題有什么更快更明確的手段來(lái)確認(rèn)這個(gè)問(wèn)題呢?(通過(guò)具體的、感性的東西來(lái)強(qiáng)化我們對(duì)知識(shí)點(diǎn)的理解和吸收。)
netstat -s
比如上面看到的 667399 times ,表示全連接隊(duì)列溢出的次數(shù),隔幾秒鐘執(zhí)行下,如果這個(gè)數(shù)字一直在增加的話肯定全連接隊(duì)列偶爾滿了。
ss 命令
上面看到的第二列Send-Q 值是50,表示第三列的listen端口上的全連接隊(duì)列最大為50,第一列Recv-Q為全連接隊(duì)列當(dāng)前使用了多少。
全連接隊(duì)列的大小取決于:min(backlog, somaxconn) . backlog是在socket創(chuàng)建的時(shí)候傳入的,somaxconn是一個(gè)os級(jí)別的系統(tǒng)參數(shù)。
這個(gè)時(shí)候可以跟我們的代碼建立聯(lián)系了,比如Java創(chuàng)建ServerSocket的時(shí)候會(huì)讓你傳入backlog的值:
(來(lái)自JDK幫助文檔:https://docs.oracle.com/javase/7/docs/api/java/net/ServerSocket.html)
半連接隊(duì)列的大小取決于:max(64, /proc/sys/net/ipv4/tcp_max_syn_backlog),不同版本的os會(huì)有些差異。
我們寫代碼的時(shí)候從來(lái)沒(méi)有想過(guò)這個(gè)backlog或者說(shuō)大多時(shí)候就沒(méi)給他值(那么默認(rèn)就是50),直接忽視了他,首先這是一個(gè)知識(shí)點(diǎn)的盲點(diǎn);其次也許哪天你在哪篇文章中看到了這個(gè)參數(shù),當(dāng)時(shí)有點(diǎn)印象,但是過(guò)一陣子就忘了,這是知識(shí)之間沒(méi)有建立連接,不是體系化的。但是如果你跟我一樣首先經(jīng)歷了這個(gè)問(wèn)題的痛苦,然后在壓力和痛苦的驅(qū)動(dòng)自己去找為什么,同時(shí)能夠把為什么從代碼層推理理解到OS層,那么這個(gè)知識(shí)點(diǎn)你才算是比較好地掌握了,也會(huì)成為你的知識(shí)體系在TCP或者性能方面成長(zhǎng)自我生長(zhǎng)的一個(gè)有力抓手。
netstat 命令
netstat跟ss命令一樣也能看到Send-Q、Recv-Q這些狀態(tài)信息,不過(guò)如果這個(gè)連接不是Listen狀態(tài)的話,Recv-Q就是指收到的數(shù)據(jù)還在緩存中,還沒(méi)被進(jìn)程讀取,這個(gè)值就是還沒(méi)被進(jìn)程讀取的 bytes;而 Send 則是發(fā)送隊(duì)列中沒(méi)有被遠(yuǎn)程主機(jī)確認(rèn)的 bytes 數(shù)。
netstat -tn 看到的 Recv-Q 跟全連接半連接沒(méi)有關(guān)系,這里特意拿出來(lái)說(shuō)一下是因?yàn)槿菀赘?ss -lnt 的 Recv-Q 搞混淆,順便建立知識(shí)體系,鞏固相關(guān)知識(shí)點(diǎn) 。
比如如下netstat -t 看到的Recv-Q有大量數(shù)據(jù)堆積,那么一般是CPU處理不過(guò)來(lái)導(dǎo)致的:
上面是通過(guò)一些具體的工具、指標(biāo)來(lái)認(rèn)識(shí)全連接隊(duì)列(工程效率的手段)。
實(shí)踐驗(yàn)證一下上面的理解
把java中backlog改成10(越小越容易溢出),繼續(xù)跑壓力,這個(gè)時(shí)候client又開始報(bào)異常了,然后在server上通過(guò) ss 命令觀察到:
按照前面的理解,這個(gè)時(shí)候我們能看到3306這個(gè)端口上的服務(wù)全連接隊(duì)列最大是10,但是現(xiàn)在有11個(gè)在隊(duì)列中和等待進(jìn)隊(duì)列的,肯定有一個(gè)連接進(jìn)不去隊(duì)列要overflow掉,同時(shí)也確實(shí)能看到overflow的值在不斷地增大。
Tomcat和Nginx中的Accept隊(duì)列參數(shù)
Tomcat默認(rèn)短連接,backlog(Tomcat里面的術(shù)語(yǔ)是Accept count)Ali-tomcat默認(rèn)是200, Apache Tomcat默認(rèn)100。
Nginx默認(rèn)是511
因?yàn)镹ginx是多進(jìn)程模式,所以看到了多個(gè)8085,也就是多個(gè)進(jìn)程都監(jiān)聽同一個(gè)端口以盡量避免上下文切換來(lái)提升性能
總結(jié)
全連接隊(duì)列、半連接隊(duì)列溢出這種問(wèn)題很容易被忽視,但是又很關(guān)鍵,特別是對(duì)于一些短連接應(yīng)用(比如Nginx、PHP,當(dāng)然他們也是支持長(zhǎng)連接的)更容易爆發(fā)。 一旦溢出,從cpu、線程狀態(tài)看起來(lái)都比較正常,但是壓力上不去,在client看來(lái)rt也比較高(rt=網(wǎng)絡(luò)+排隊(duì)+真正服務(wù)時(shí)間),但是從server日志記錄的真正服務(wù)時(shí)間來(lái)看rt又很短。
jdk、netty等一些框架默認(rèn)backlog比較小,可能有些情況下導(dǎo)致性能上不去。
希望通過(guò)本文能夠幫大家理解TCP連接過(guò)程中的半連接隊(duì)列和全連接隊(duì)列的概念、原理和作用,更關(guān)鍵的是有哪些指標(biāo)可以明確看到這些問(wèn)題(工程效率幫助強(qiáng)化對(duì)理論的理解)。
另外每個(gè)具體問(wèn)題都是最好學(xué)習(xí)的機(jī)會(huì),光看書理解肯定是不夠深刻的,請(qǐng)珍惜每個(gè)具體問(wèn)題,碰到后能夠把來(lái)龍去脈弄清楚,每個(gè)問(wèn)題都是你對(duì)具體知識(shí)點(diǎn)通關(guān)的好機(jī)會(huì)。
最后提出相關(guān)問(wèn)題給大家思考
全連接隊(duì)列滿了會(huì)影響半連接隊(duì)列嗎?
netstat -s看到的overflowed和ignored的數(shù)值有什么聯(lián)系嗎?
如果client走完了TCP握手的第三步,在client看來(lái)連接已經(jīng)建立好了,但是server上的對(duì)應(yīng)連接實(shí)際沒(méi)有準(zhǔn)備好,這個(gè)時(shí)候如果client發(fā)數(shù)據(jù)給server,server會(huì)怎么處理呢?(有同學(xué)說(shuō)會(huì)reset,你覺得呢?)
提出這些問(wèn)題,希望以這個(gè)知識(shí)點(diǎn)為抓手,讓你的知識(shí)體系開始自我生長(zhǎng)。
參考文章
http://veithen.github.io/2014/01/01/how-tcp-backlog-works-in-linux.html
http://www.cnblogs.com/zengkefu/p/5606696.html
http://www.cnxct.com/something-about-phpfpm-s-backlog/
http://jaseywang.me/2014/07/20/tcp-queue-%E7%9A%84%E4%B8%80%E4%BA%9B%E9%97%AE%E9%A2%98/
http://jin-yang.github.io/blog/network-synack-queue.html#
http://blog.chinaunix.net/uid-20662820-id-4154399.html
【本文為51CTO專欄作者“阿里巴巴官方技術(shù)”原創(chuàng)稿件,轉(zhuǎn)載請(qǐng)聯(lián)系原作者】