一個(gè)項(xiàng)目對(duì)接第三方接口數(shù)據(jù)。對(duì)方是TCP接口,發(fā)送數(shù)據(jù)頻率很高。平均2毫秒發(fā)送三四千個(gè)字節(jié)。由于TCP協(xié)議的粘包拆包問(wèn)題,我這里接收到的數(shù)據(jù)需要對(duì)粘包拆包按照對(duì)方數(shù)據(jù)的格式進(jìn)行處理。對(duì)接了一段時(shí)間后發(fā)現(xiàn),TCP連接會(huì)自動(dòng)斷開(kāi)。由于我這里做了斷開(kāi)重連的邏輯。所以最終的現(xiàn)象就是一直在斷開(kāi),重連,再斷開(kāi),再重連。
向數(shù)據(jù)提供方咨詢,數(shù)據(jù)提供方給出的反饋是數(shù)據(jù)消費(fèi)不過(guò)來(lái),造成數(shù)據(jù)積壓后,他們的程序就會(huì)主動(dòng)斷開(kāi)TCP連接。通過(guò)日志發(fā)現(xiàn),我這里確實(shí)在斷開(kāi)前,消費(fèi)數(shù)據(jù)出現(xiàn)了延遲。通過(guò)日志觀察發(fā)現(xiàn),接收數(shù)據(jù)的時(shí)候,比發(fā)送數(shù)據(jù)的時(shí)間慢了幾秒,由于數(shù)據(jù)量很大,所以造成了積壓,數(shù)據(jù)提供方就斷開(kāi)了連接。
問(wèn)題分析
于是,問(wèn)題的焦點(diǎn)就到了為何我的程序消費(fèi)不過(guò)來(lái)數(shù)據(jù)呢?首先想到的就是我寫(xiě)的程序性能有問(wèn)題,導(dǎo)致無(wú)法消費(fèi)平均2毫秒產(chǎn)生的三四千個(gè)字節(jié)這個(gè)數(shù)據(jù)頻率。由于粘包拆包程序是我自己自定義處理的,于是,我懷疑是自己的處理邏輯性能差。
通過(guò)研究發(fā)現(xiàn),netty框架提供了針對(duì)于TCP粘包拆包的解析類(lèi)。于是,我引入了netty框架,使用netty框架提供的LengthFieldBasedFrameDecoder解析器對(duì)接收到的數(shù)據(jù)進(jìn)行處理。發(fā)現(xiàn)還是會(huì)出現(xiàn)延遲,消費(fèi)不過(guò)來(lái)的現(xiàn)象發(fā)生。
由于netty接收數(shù)據(jù)后,對(duì)數(shù)據(jù)進(jìn)行處理,默認(rèn)是使用單線程來(lái)完成的。即接收TCP數(shù)據(jù)和處理粘包拆包是在一個(gè)線程中完成的。這是不是影響了消費(fèi)的速率呢?于是,我寫(xiě)了兩個(gè)線程,一個(gè)用于接收數(shù)據(jù),然后把接收到的數(shù)據(jù)存入一個(gè)集合容器中。緊接著繼續(xù)拉取下一批數(shù)據(jù)。另一個(gè)線程用于處理集合容器中的數(shù)據(jù)。這種解決方案依舊不行,還是延遲消費(fèi)數(shù)據(jù)。
難道是接收了數(shù)據(jù),往集合容器中存放這個(gè)操作,也影響了性能,使其消費(fèi)不過(guò)來(lái)了?于是,我把程序改成了只接收數(shù)據(jù),然后打印一個(gè)接收字節(jié)數(shù)的日志,其余的操作再也沒(méi)有了。嘗試這種操作是否可以不自動(dòng)斷開(kāi)TCP連接。因?yàn)椴蛔詣?dòng)斷開(kāi)TCP連接證明數(shù)據(jù)消費(fèi)沒(méi)有延遲。
令人費(fèi)解的事情發(fā)生了,純接收數(shù)據(jù),就打印個(gè)接收字節(jié)數(shù)的日志,還是會(huì)自動(dòng)斷開(kāi)TCP連接。這就表明,無(wú)論我怎么優(yōu)化代碼,它都會(huì)延遲消費(fèi)。這就不是我代碼的問(wèn)題而引起的消費(fèi)延遲了。因?yàn)槲铱隙ㄒソ邮誘CP的數(shù)據(jù)。而現(xiàn)在純接收數(shù)據(jù),不做任何處理,就發(fā)生了延遲了。這說(shuō)明已經(jīng)不是我程序代碼的問(wèn)題了。
起初懷疑的對(duì)象是操作系統(tǒng)是不是消費(fèi)不過(guò)來(lái)了?一定是操作系統(tǒng)先從網(wǎng)絡(luò)中拉取數(shù)據(jù),我的應(yīng)用程序再?gòu)牟倏v系統(tǒng)中獲取數(shù)據(jù)的。如果操作系統(tǒng)這個(gè)層面就消費(fèi)不過(guò)來(lái)了,那么我的程序肯定也消費(fèi)不過(guò)來(lái)。因?yàn)槲矣玫姆?wù)器是Window Server系統(tǒng),所以,將程序部署到了一臺(tái)linux服務(wù)器上進(jìn)行純接收數(shù)據(jù),不做任何處理的測(cè)試。最后發(fā)現(xiàn),依舊會(huì)自動(dòng)斷開(kāi)TCP連接。
服務(wù)器這個(gè)猜測(cè)也失敗了。因?yàn)閿?shù)據(jù)提供方也會(huì)消費(fèi)這些數(shù)據(jù),而他們的系統(tǒng)沒(méi)有出現(xiàn)過(guò)這種自動(dòng)斷開(kāi)的情況。說(shuō)明不是操作系統(tǒng)消費(fèi)不過(guò)來(lái)了。
于是,我把目光轉(zhuǎn)到了接收字節(jié)數(shù)量的日志上。發(fā)現(xiàn)大多數(shù)日志輸出的都是一次拉取1460個(gè)字節(jié)。還發(fā)現(xiàn)會(huì)有一次拉取幾萬(wàn)個(gè)字節(jié)的情況出現(xiàn)。而最大的拉取量是65536個(gè)字節(jié),不會(huì)比這個(gè)字節(jié)再大了。即使我在程序里定義的讀取數(shù)據(jù)的byte數(shù)組的長(zhǎng)度是10萬(wàn),程序最多也是拉取65536個(gè)字節(jié)。
搞不懂這些數(shù)字代表的含義,可以看看下面這篇文章:
深入理解 TCP 協(xié)議:從原理到實(shí)戰(zhàn)【超詳細(xì)】-上
深入理解 TCP 協(xié)議:從原理到實(shí)戰(zhàn)【超詳細(xì)】-下
這里解釋一下上述日志中幾個(gè)數(shù)字的含義。首先,1460是以太網(wǎng)的MTU是1500,去掉40個(gè)字節(jié)的TCP頭和IP頭,業(yè)務(wù)數(shù)據(jù)的長(zhǎng)度就是1460個(gè)字節(jié)。即一個(gè)包最長(zhǎng)的業(yè)務(wù)數(shù)據(jù)就是1460。而程序每次大部分都讀取1460個(gè)字節(jié)。證明滑動(dòng)窗口里的數(shù)據(jù)是沒(méi)有積壓的。也就是說(shuō)當(dāng)程序讀1460個(gè)字節(jié)的時(shí)候,說(shuō)明是消費(fèi)的過(guò)來(lái)的。因?yàn)槿绻麛?shù)據(jù)積壓了,那么必定在滑動(dòng)窗口里有很多個(gè)字節(jié),甚至把滑動(dòng)窗口填滿。那么程序單次拉取字節(jié)數(shù),就不可能是1460個(gè),而是比1460個(gè)要多。所以當(dāng)程序每次拉取也是1460的時(shí)候,說(shuō)明發(fā)一次數(shù)據(jù),就可以消費(fèi)一次數(shù)據(jù),是不存在延遲現(xiàn)象的。
那么日志中小于1460個(gè)數(shù)據(jù)拉取又是如何發(fā)生的呢?加入TCP的發(fā)送方一次發(fā)送了2000個(gè)字節(jié)的業(yè)務(wù)數(shù)據(jù),而在物理層的以太網(wǎng)中,只能發(fā)送1460個(gè)字節(jié)。那么就會(huì)對(duì)數(shù)據(jù)進(jìn)行分片。前1460個(gè)字節(jié)為一個(gè)包,發(fā)送獲取了,剩余的540個(gè)字節(jié)是另一個(gè)包發(fā)送過(guò)去。這就造成了少于1460個(gè)字節(jié)的拉取情況出現(xiàn)。其本質(zhì)也是發(fā)送了多少數(shù)據(jù)就拉取了多少數(shù)據(jù),不存在延遲現(xiàn)象。
那么延遲到底是怎么發(fā)生的呢?這就要分析那些一次拉取上萬(wàn)個(gè)字節(jié)的數(shù)據(jù)的情況是如何發(fā)生的了。通過(guò)觀察發(fā)現(xiàn),每次拉取上萬(wàn)個(gè)字節(jié)的數(shù)據(jù),日志都會(huì)卡頓幾十毫秒甚至更長(zhǎng)才會(huì)拉取一次數(shù)據(jù)。由于停頓的這幾十毫秒一直有數(shù)據(jù)發(fā)送過(guò)來(lái),所以接收滑動(dòng)窗口的數(shù)據(jù)就會(huì)一直增加。當(dāng)停頓結(jié)束后再次拉取數(shù)據(jù)時(shí),就從滑動(dòng)窗口里拉取了更多的數(shù)據(jù)回來(lái)。所以就有上萬(wàn)個(gè)字節(jié)的數(shù)據(jù)了。那程序?yàn)楹螘?huì)停頓,不拉取數(shù)據(jù)了呢?
通過(guò)wireshark抓包工具發(fā)現(xiàn),當(dāng)TCP出現(xiàn)丟包時(shí),程序就不拉取數(shù)據(jù)了。因?yàn)楫?dāng)TCP丟包后,由于滑動(dòng)窗口的存在,在滑動(dòng)窗口范圍內(nèi),還會(huì)繼續(xù)接收發(fā)送過(guò)來(lái)的數(shù)據(jù)。但是因?yàn)閬G包了,所以應(yīng)用程序就不會(huì)再消費(fèi)數(shù)據(jù)了。此時(shí),我這里的操作系統(tǒng)會(huì)給發(fā)送方反饋丟包信息(TCP dup ack),默認(rèn)情況下是發(fā)送三次TCP dup ack后,發(fā)送方就會(huì)立馬重傳丟包的數(shù)據(jù)。但是觀察wireshark發(fā)現(xiàn),丟包后,重傳50多次TCP dup ack后,發(fā)送方才會(huì)返回丟包的數(shù)據(jù)。這就說(shuō)明,TCP dup ack反饋消息也在一直丟失,直到發(fā)送了50多次后,才收到3條TCP dup ack信息。當(dāng)然,如果一直收不到TCP dup ack信息,那么只有等到超時(shí)時(shí)間后,發(fā)送方主動(dòng)再次發(fā)送丟包數(shù)據(jù)了。可見(jiàn),這里的網(wǎng)絡(luò)環(huán)境是有多差。
那這就分析出了消費(fèi)延遲的根本問(wèn)題所在。因?yàn)門(mén)CP發(fā)生了丟包,導(dǎo)致了應(yīng)用程序的停頓,無(wú)法讀取TCP丟包后的數(shù)據(jù)。而丟包的反饋TCP dup ack又無(wú)法第一時(shí)間被發(fā)送方接收到,所以接收方應(yīng)用程序卡頓的時(shí)間就會(huì)變長(zhǎng)。而TCP產(chǎn)生數(shù)據(jù)的頻率又是很高的,所以在停頓的這個(gè)期間,就產(chǎn)生了很多的數(shù)據(jù)。當(dāng)丟包的數(shù)據(jù)被發(fā)送方發(fā)送過(guò)來(lái)后,應(yīng)用程序從丟包位置讀取數(shù)據(jù),而此時(shí)丟包的位置后面已經(jīng)產(chǎn)生了大量的數(shù)據(jù),所以造成了消費(fèi)的延遲。
分析到這里,問(wèn)題的本質(zhì)似乎找到了。但是還有一個(gè)現(xiàn)象不可忽略。那就是每次TCP主動(dòng)斷開(kāi)的位置,都不是程序停頓的位置,也不是一次拉取幾萬(wàn)個(gè)字節(jié)的位置。而是一次拉取1460個(gè)字節(jié)的位置。而上面我們又分析道,一次拉取1460個(gè)字節(jié),說(shuō)明消費(fèi)是能跟的上的,沒(méi)有積壓數(shù)據(jù)。那為何還會(huì)消費(fèi)延遲呢?
這就涉及到了TCP網(wǎng)絡(luò)擁堵的處理機(jī)制。只要發(fā)生了丟包,TCP就認(rèn)為當(dāng)前的網(wǎng)絡(luò)環(huán)境不佳,TCP發(fā)送方會(huì)根據(jù)自己的機(jī)制,主動(dòng)減少發(fā)送量,避免對(duì)網(wǎng)絡(luò)造成更大的壓力。當(dāng)發(fā)生丟包后,應(yīng)用程序停頓了幾秒。但是停頓結(jié)束后,會(huì)有幾次拉取幾萬(wàn)個(gè)字節(jié)數(shù)據(jù)的情況,這幾次拉取,就會(huì)趕上積壓的數(shù)據(jù)的消費(fèi)速率。也就是這里會(huì)有延遲,但是拉取幾次大數(shù)據(jù)量后,消費(fèi)就趕上來(lái)了。而TCP發(fā)送方認(rèn)為當(dāng)前的網(wǎng)絡(luò)環(huán)境不佳,所以發(fā)送方主動(dòng)減少了發(fā)送的吞吐量。這就造成了發(fā)送方產(chǎn)生了大量的數(shù)據(jù),但是發(fā)送的數(shù)據(jù)量很小。這就造成了發(fā)送方數(shù)據(jù)的積壓。當(dāng)積壓到一定程度后,發(fā)送方的應(yīng)用程序就斷開(kāi)了連接。
總結(jié)
綜上所述,發(fā)生消費(fèi)延遲問(wèn)題,是因?yàn)門(mén)CP頻繁丟包,觸發(fā)了TCP的擁堵處理機(jī)制,導(dǎo)致發(fā)送方發(fā)送量減少,數(shù)據(jù)產(chǎn)生了積壓,造成了消費(fèi)的延遲。
上面還有一點(diǎn)提到數(shù)據(jù)積壓后,最大的拉取量是65536,這是因?yàn)椴僮飨到y(tǒng)所能承受的單次數(shù)據(jù)拉取量,就是65535個(gè),如果頻繁的接收大于65535字節(jié)的數(shù)據(jù),會(huì)使操作系統(tǒng)崩潰。所以這個(gè)值是操作系統(tǒng)進(jìn)行的限制。而我上面又說(shuō)到,數(shù)據(jù)提供方也在消費(fèi)這個(gè)數(shù)據(jù),但是他們沒(méi)有出現(xiàn)過(guò)延遲。是因?yàn)樗麄冊(cè)诒镜剡M(jìn)行的數(shù)據(jù)消費(fèi)。即數(shù)據(jù)發(fā)送和接收都是一個(gè)服務(wù)器。這種情況是不會(huì)走物理層的,也不會(huì)經(jīng)過(guò)網(wǎng)卡,所以單次傳輸就沒(méi)有1460這個(gè)限制了,就升級(jí)到了65535這個(gè)限制。而且本地傳輸也不會(huì)出現(xiàn)丟包現(xiàn)象。所以他們消費(fèi)沒(méi)出現(xiàn)延遲。
感悟
TCP為了保證數(shù)據(jù)的安全性,發(fā)生丟包后做出的一系列處理會(huì)影響性能。而在大數(shù)量的情況下,會(huì)把性能的影響放大。所以,在選擇協(xié)議時(shí),要綜合分析,選擇最合適的協(xié)議。例如本次的業(yè)務(wù)場(chǎng)景,完全不適合用TCP協(xié)議,而適合用UDP協(xié)議。因?yàn)榻邮盏綌?shù)據(jù)后,也會(huì)根據(jù)邏輯過(guò)濾掉大多數(shù)的數(shù)據(jù),而是保留一部分?jǐn)?shù)據(jù)。所以對(duì)數(shù)據(jù)的安全性要求不是那么高,使用UDP協(xié)議,允許丟失一部分?jǐn)?shù)據(jù),就不會(huì)出現(xiàn)這種消費(fèi)延遲的問(wèn)題了。
抓包記錄
記錄一下使用wireshark抓包發(fā)現(xiàn)問(wèn)題的過(guò)程。
首先,選擇要監(jiān)聽(tīng)的網(wǎng)卡,然后輸入過(guò)濾器,過(guò)濾ip(host xxx),只查看發(fā)送數(shù)據(jù)的ip的TCP協(xié)議。
然后,進(jìn)入網(wǎng)絡(luò)監(jiān)控頁(yè)面,在頂部的過(guò)濾器中,輸入tcp,表示只監(jiān)控tcp協(xié)議。
第二列的TIme字段,默認(rèn)不是yyyy-MM-dd HH:mm:ss格式的,需要手動(dòng)調(diào)成此格式,方便查看數(shù)據(jù)發(fā)送的時(shí)間:
然后,開(kāi)始分析接收到的具體數(shù)據(jù):
如上圖所示,TCP Previous segment not captured就代表接收方收到了后面的數(shù)據(jù),但是前面的數(shù)據(jù)還沒(méi)收到。即前面的數(shù)據(jù)發(fā)生了丟包。這個(gè)提示是wireshark自己分析給出的提示,而且還標(biāo)黑了,說(shuō)明接收數(shù)據(jù)發(fā)生了丟包。
發(fā)生丟包后,接收方就給發(fā)送方發(fā)送TCP Dup Ack信息,告訴接收方丟包了。默認(rèn)情況是發(fā)送三次,接收方就會(huì)把丟包數(shù)據(jù)返回,但是如上圖所示,發(fā)送了11次,還沒(méi)收到丟包數(shù)據(jù)。
直到發(fā)送了58次以后,才收到發(fā)送方返回的 TCP Fast Retransmission信息,這表明是收到了丟包的數(shù)據(jù)。
此外,在統(tǒng)計(jì)–>專(zhuān)家信息中,wireshark也統(tǒng)計(jì)了各種情況發(fā)生的次數(shù),如下圖:
第一行就是丟包的次數(shù),可見(jiàn),發(fā)生了85次丟包。丟包現(xiàn)象很?chē)?yán)重。
-
接口
+關(guān)注
關(guān)注
33文章
8952瀏覽量
153229 -
數(shù)據(jù)
+關(guān)注
關(guān)注
8文章
7242瀏覽量
91046 -
TCP
+關(guān)注
關(guān)注
8文章
1397瀏覽量
80415 -
程序
+關(guān)注
關(guān)注
117文章
3824瀏覽量
82451
發(fā)布評(píng)論請(qǐng)先 登錄
常見(jiàn)的網(wǎng)絡(luò)丟包故障定位?法

esp8266透?jìng)?b class='flag-5'>tcp如何防止丟包?
AD9122 REFIO管腳沒(méi)有外接負(fù)載,如果沒(méi)有按手冊(cè)外接0.1uF電容濾波,對(duì)AD9122性能究竟會(huì)有什么不良影響?
網(wǎng)絡(luò)數(shù)據(jù)丟包的原因及攝像機(jī)丟包的原因
Linux應(yīng)用的延時(shí)和丟包模擬
直角走線究竟會(huì)對(duì)信號(hào)傳輸產(chǎn)生多大的影響?資料下載

PCB直角走線究竟會(huì)對(duì)信號(hào)傳輸產(chǎn)生多大的影響?資料下載

深入分析Linux網(wǎng)絡(luò)丟包問(wèn)題

丟包問(wèn)題如何解決?方法在這里

網(wǎng)絡(luò)丟包問(wèn)題解析

網(wǎng)絡(luò)丟包故障如何定位

網(wǎng)絡(luò)丟包問(wèn)題分析

評(píng)論