一個項目對接第三方接口數據。對方是TCP接口,發送數據頻率很高。平均2毫秒發送三四千個字節。由于TCP協議的粘包拆包問題,我這里接收到的數據需要對粘包拆包按照對方數據的格式進行處理。對接了一段時間后發現,TCP連接會自動斷開。由于我這里做了斷開重連的邏輯。所以最終的現象就是一直在斷開,重連,再斷開,再重連。
向數據提供方咨詢,數據提供方給出的反饋是數據消費不過來,造成數據積壓后,他們的程序就會主動斷開TCP連接。通過日志發現,我這里確實在斷開前,消費數據出現了延遲。通過日志觀察發現,接收數據的時候,比發送數據的時間慢了幾秒,由于數據量很大,所以造成了積壓,數據提供方就斷開了連接。
問題分析
于是,問題的焦點就到了為何我的程序消費不過來數據呢?首先想到的就是我寫的程序性能有問題,導致無法消費平均2毫秒產生的三四千個字節這個數據頻率。由于粘包拆包程序是我自己自定義處理的,于是,我懷疑是自己的處理邏輯性能差。
通過研究發現,netty框架提供了針對于TCP粘包拆包的解析類。于是,我引入了netty框架,使用netty框架提供的LengthFieldBasedFrameDecoder解析器對接收到的數據進行處理。發現還是會出現延遲,消費不過來的現象發生。
由于netty接收數據后,對數據進行處理,默認是使用單線程來完成的。即接收TCP數據和處理粘包拆包是在一個線程中完成的。這是不是影響了消費的速率呢?于是,我寫了兩個線程,一個用于接收數據,然后把接收到的數據存入一個集合容器中。緊接著繼續拉取下一批數據。另一個線程用于處理集合容器中的數據。這種解決方案依舊不行,還是延遲消費數據。
難道是接收了數據,往集合容器中存放這個操作,也影響了性能,使其消費不過來了?于是,我把程序改成了只接收數據,然后打印一個接收字節數的日志,其余的操作再也沒有了。嘗試這種操作是否可以不自動斷開TCP連接。因為不自動斷開TCP連接證明數據消費沒有延遲。
令人費解的事情發生了,純接收數據,就打印個接收字節數的日志,還是會自動斷開TCP連接。這就表明,無論我怎么優化代碼,它都會延遲消費。這就不是我代碼的問題而引起的消費延遲了。因為我肯定要去接收TCP的數據。而現在純接收數據,不做任何處理,就發生了延遲了。這說明已經不是我程序代碼的問題了。
起初懷疑的對象是操作系統是不是消費不過來了?一定是操作系統先從網絡中拉取數據,我的應用程序再從操縱系統中獲取數據的。如果操作系統這個層面就消費不過來了,那么我的程序肯定也消費不過來。因為我用的服務器是Window Server系統,所以,將程序部署到了一臺linux服務器上進行純接收數據,不做任何處理的測試。最后發現,依舊會自動斷開TCP連接。
服務器這個猜測也失敗了。因為數據提供方也會消費這些數據,而他們的系統沒有出現過這種自動斷開的情況。說明不是操作系統消費不過來了。
于是,我把目光轉到了接收字節數量的日志上。發現大多數日志輸出的都是一次拉取1460個字節。還發現會有一次拉取幾萬個字節的情況出現。而最大的拉取量是65536個字節,不會比這個字節再大了。即使我在程序里定義的讀取數據的byte數組的長度是10萬,程序最多也是拉取65536個字節。
搞不懂這些數字代表的含義,可以看看下面這篇文章:
這里解釋一下上述日志中幾個數字的含義。首先,1460是以太網的MTU是1500,去掉40個字節的TCP頭和IP頭,業務數據的長度就是1460個字節。即一個包最長的業務數據就是1460。而程序每次大部分都讀取1460個字節。證明滑動窗口里的數據是沒有積壓的。也就是說當程序讀1460個字節的時候,說明是消費的過來的。因為如果數據積壓了,那么必定在滑動窗口里有很多個字節,甚至把滑動窗口填滿。那么程序單次拉取字節數,就不可能是1460個,而是比1460個要多。所以當程序每次拉取也是1460的時候,說明發一次數據,就可以消費一次數據,是不存在延遲現象的。
那么日志中小于1460個數據拉取又是如何發生的呢?加入TCP的發送方一次發送了2000個字節的業務數據,而在物理層的以太網中,只能發送1460個字節。那么就會對數據進行分片。前1460個字節為一個包,發送獲取了,剩余的540個字節是另一個包發送過去。這就造成了少于1460個字節的拉取情況出現。其本質也是發送了多少數據就拉取了多少數據,不存在延遲現象。
那么延遲到底是怎么發生的呢?這就要分析那些一次拉取上萬個字節的數據的情況是如何發生的了。通過觀察發現,每次拉取上萬個字節的數據,日志都會卡頓幾十毫秒甚至更長才會拉取一次數據。由于停頓的這幾十毫秒一直有數據發送過來,所以接收滑動窗口的數據就會一直增加。當停頓結束后再次拉取數據時,就從滑動窗口里拉取了更多的數據回來。所以就有上萬個字節的數據了。那程序為何會停頓,不拉取數據了呢?
通過wireshark抓包工具發現,當TCP出現丟包時,程序就不拉取數據了。因為當TCP丟包后,由于滑動窗口的存在,在滑動窗口范圍內,還會繼續接收發送過來的數據。但是因為丟包了,所以應用程序就不會再消費數據了。此時,我這里的操作系統會給發送方反饋丟包信息(TCP dup ack),默認情況下是發送三次TCP dup ack后,發送方就會立馬重傳丟包的數據。但是觀察wireshark發現,丟包后,重傳50多次TCP dup ack后,發送方才會返回丟包的數據。這就說明,TCP dup ack反饋消息也在一直丟失,直到發送了50多次后,才收到3條TCP dup ack信息。當然,如果一直收不到TCP dup ack信息,那么只有等到超時時間后,發送方主動再次發送丟包數據了??梢?,這里的網絡環境是有多差。
那這就分析出了消費延遲的根本問題所在。因為TCP發生了丟包,導致了應用程序的停頓,無法讀取TCP丟包后的數據。而丟包的反饋TCP dup ack又無法第一時間被發送方接收到,所以接收方應用程序卡頓的時間就會變長。而TCP產生數據的頻率又是很高的,所以在停頓的這個期間,就產生了很多的數據。當丟包的數據被發送方發送過來后,應用程序從丟包位置讀取數據,而此時丟包的位置后面已經產生了大量的數據,所以造成了消費的延遲。
分析到這里,問題的本質似乎找到了。但是還有一個現象不可忽略。那就是每次TCP主動斷開的位置,都不是程序停頓的位置,也不是一次拉取幾萬個字節的位置。而是一次拉取1460個字節的位置。而上面我們又分析道,一次拉取1460個字節,說明消費是能跟的上的,沒有積壓數據。那為何還會消費延遲呢?
這就涉及到了TCP網絡擁堵的處理機制。只要發生了丟包,TCP就認為當前的網絡環境不佳,TCP發送方會根據自己的機制,主動減少發送量,避免對網絡造成更大的壓力。當發生丟包后,應用程序停頓了幾秒。但是停頓結束后,會有幾次拉取幾萬個字節數據的情況,這幾次拉取,就會趕上積壓的數據的消費速率。也就是這里會有延遲,但是拉取幾次大數據量后,消費就趕上來了。而TCP發送方認為當前的網絡環境不佳,所以發送方主動減少了發送的吞吐量。這就造成了發送方產生了大量的數據,但是發送的數據量很小。這就造成了發送方數據的積壓。當積壓到一定程度后,發送方的應用程序就斷開了連接。
總結
綜上所述,發生消費延遲問題,是因為TCP頻繁丟包,觸發了TCP的擁堵處理機制,導致發送方發送量減少,數據產生了積壓,造成了消費的延遲。
上面還有一點提到數據積壓后,最大的拉取量是65536,這是因為操作系統所能承受的單次數據拉取量,就是65535個,如果頻繁的接收大于65535字節的數據,會使操作系統崩潰。所以這個值是操作系統進行的限制。而我上面又說到,數據提供方也在消費這個數據,但是他們沒有出現過延遲。是因為他們在本地進行的數據消費。即數據發送和接收都是一個服務器。這種情況是不會走物理層的,也不會經過網卡,所以單次傳輸就沒有1460這個限制了,就升級到了65535這個限制。而且本地傳輸也不會出現丟包現象。所以他們消費沒出現延遲。
感悟
TCP為了保證數據的安全性,發生丟包后做出的一系列處理會影響性能。而在大數量的情況下,會把性能的影響放大。所以,在選擇協議時,要綜合分析,選擇最合適的協議。例如本次的業務場景,完全不適合用TCP協議,而適合用UDP協議。因為接收到數據后,也會根據邏輯過濾掉大多數的數據,而是保留一部分數據。所以對數據的安全性要求不是那么高,使用UDP協議,允許丟失一部分數據,就不會出現這種消費延遲的問題了。
抓包記錄
記錄一下使用wireshark抓包發現問題的過程。
首先,選擇要監聽的網卡,然后輸入過濾器,過濾ip(host xxx),只查看發送數據的ip的TCP協議。
然后,進入網絡監控頁面,在頂部的過濾器中,輸入tcp,表示只監控tcp協議。
第二列的TIme字段,默認不是yyyy-MM-dd HH:mm:ss格式的,需要手動調成此格式,方便查看數據發送的時間:
然后,開始分析接收到的具體數據:
如上圖所示,TCP Previous segment not captured就代表接收方收到了后面的數據,但是前面的數據還沒收到。即前面的數據發生了丟包。這個提示是wireshark自己分析給出的提示,而且還標黑了,說明接收數據發生了丟包。
發生丟包后,接收方就給發送方發送TCP Dup Ack信息,告訴接收方丟包了。默認情況是發送三次,接收方就會把丟包數據返回,但是如上圖所示,發送了11次,還沒收到丟包數據。
直到發送了58次以后,才收到發送方返回的 TCP Fast Retransmission信息,這表明是收到了丟包的數據。
此外,在統計–>專家信息中,wireshark也統計了各種情況發生的次數,如下圖:
第一行就是丟包的次數,可見,發生了85次丟包。丟包現象很嚴重。
-
接口
+關注
關注
33文章
8526瀏覽量
150862 -
數據
+關注
關注
8文章
6909瀏覽量
88849 -
TCP
+關注
關注
8文章
1351瀏覽量
78995 -
程序
+關注
關注
116文章
3778瀏覽量
80860
發布評論請先 登錄
相關推薦
評論