今天分享一篇文章,是關(guān)于 TCP 擁塞控制對數(shù)據(jù)延遲產(chǎn)生的影響的。作者在服務(wù)延遲變高之后進(jìn)行抓包分析,結(jié)果發(fā)現(xiàn)時間花在了 TCP 本身的機(jī)制上面:客戶端并不是將請求一股腦發(fā)送給服務(wù)端,而是只發(fā)送了一部分,等到接收到服務(wù)端的 ACK,然后繼續(xù)再發(fā)送,這就造成了額外的 RTT,這個額外的 RTT 是由 TCP 的擁塞控制導(dǎo)致的
這是上周在項目上遇到的一個問題,在內(nèi)網(wǎng)把問題用英文分析了一遍,覺得挺有用的,所以在博客上打算再寫一次。
問題是這樣的:我們在當(dāng)前的環(huán)境中,網(wǎng)絡(luò)延遲 <1ms,服務(wù)的延遲是 2ms,現(xiàn)在要遷移到一個新的環(huán)境,新的環(huán)境網(wǎng)絡(luò)自身延遲(來回的延遲,RTT,本文中談到延遲都指的是 RTT 延遲)是 100ms,那么請問,服務(wù)的延遲應(yīng)該是多少?
我們的預(yù)期是 102ms 左右,但是現(xiàn)實(shí)中,發(fā)現(xiàn)實(shí)際的延遲漲了不止 100ms,P99 到了 300ms 左右。
從日志中,發(fā)現(xiàn)有請求的延遲的確很高,但是模式就是 200ms, 300ms 甚至 400ms 左右,看起來是多花了幾個 RTT。
接下來就根據(jù)日志去抓包,最后發(fā)現(xiàn),時間花在了 TCP 本身的機(jī)制上面,這些高延遲的請求都發(fā)生在 TCP 創(chuàng)建連接之后。
首先是 TCP 創(chuàng)建連接的時間,TCP 創(chuàng)建連接需要三次握手,需要額外增加一個 RTT。為什么不是兩個 RTT?因為過程是這樣的:
+0 A -> B SYN +0.5RTT B -> A SYN+ACK +1RTT A -> B ACK +1RTT A -> B Data
即第三個包,在 A 發(fā)給 B 之后,A 就繼續(xù)發(fā)送下面的數(shù)據(jù)了,所以可以認(rèn)為這第三個包不會占用額外的時間。
這樣的話,延遲會額外增加一個 RTT,加上本身數(shù)據(jù)傳輸?shù)囊粋€ RTT,那么,我們能觀察到的最高的 RTT 應(yīng)該是 2 個 RTT,即 200ms,那么為什么會看到 400ms 的請求呢?
從抓包分析看,我發(fā)現(xiàn)在建立 TCP 連接之后,客戶端并不是將請求一股腦發(fā)送給服務(wù)端,而是只發(fā)送了一部分,等到接收到服務(wù)端的 ACK,然后繼續(xù)在發(fā)送,這就造成了額外的 RTT??吹竭@里我恍然大悟,原來是 cwnd 造成的。
cwnd 如何分析,之前的博文中也提到過。簡單來說,這是 TCP 層面的一個機(jī)制,為了避免網(wǎng)絡(luò)賽車,在建立 TCP 連接之后,發(fā)送端并不知道這個網(wǎng)絡(luò)到底能承受多大的流量,所以發(fā)送端會發(fā)送一部分?jǐn)?shù)據(jù),如果 OK,滿滿加大發(fā)送數(shù)據(jù)的量。這就是 TCP 的慢啟動。
那么慢啟動從多少開始呢?
Linux 中默認(rèn)是 10.
/usr/src/linux/include/net/tcp.h: /* TCP initial congestion window as per draft-hkchu-tcpm-initcwnd-01 */ #define TCP_INIT_CWND 10
也就是說,在小于 cwnd=10 * MSS=1448bytes = 14480bytes 數(shù)據(jù)的情況下,我們可以用 2 RTT 發(fā)送完畢數(shù)據(jù)。即 1 個 RTT 用于建立 TCP 連接,1個 RTT 用于發(fā)送數(shù)據(jù)。
下面這個抓包可以證明這一點(diǎn),我在 100ms 的環(huán)境中,從一端發(fā)送了正好 14480 的數(shù)據(jù),恰好是用了 200ms:
100ms 用于建立連接,100ms 用于發(fā)送數(shù)據(jù)
如果發(fā)送的數(shù)據(jù)小于 14480 bytes(大約是 14K),那么用的時間應(yīng)該是一樣的。
但是,如果多了即使 1 byte,延遲也會增加一個 RTT,即需要 300ms。下面是發(fā)送 14481 bytes 的抓包情況:
多出來一個 100ms 用于傳輸這個額外的 byte
慢啟動,顧名思義,只發(fā)生在啟動階段,如果第一波發(fā)出去的數(shù)據(jù)都能收到確認(rèn),那么證明網(wǎng)絡(luò)的容量足夠,可以一次性發(fā)送更多的數(shù)據(jù),這時 cwnd 就會繼續(xù)增大了(取決于具體擁塞控制的算法)。
這就是額外的延遲的來源了?;氐轿覀兊陌咐@個用戶的請求大約是 30K,響應(yīng)也大約是 30K,而 cwnd 是雙向的,即兩端分別進(jìn)行慢啟動,所以,請求發(fā)送過來 +1 RTT,響應(yīng) +1 RTT,TCP 建立連接 +1 RTT,加上本身數(shù)據(jù)傳輸就有 1 RTT,總共 4RTT,就解釋的通了。
解決辦法也很簡單,兩個問題都可以使用 TCP 長連接來解決。
PS:其實(shí),到這里讀者應(yīng)該發(fā)現(xiàn),這個服務(wù)本身的延遲,在這種情況下,也是 4個 RTT,只不過網(wǎng)絡(luò)環(huán)境 A 的延遲很小,在 1ms 左右,這樣服務(wù)自己處理請求的延遲要遠(yuǎn)大于網(wǎng)絡(luò)的延遲,1 個 RTT 和 4 個 RTT 從監(jiān)控上幾乎看不出區(qū)別。
PPS:其實(shí),以上內(nèi)容,比如 “慢啟動,顧名思義,只發(fā)生在啟動階段“,以及 ”兩個問題都可以使用 TCP 長連接來解決“ 的表述是不準(zhǔn)確的,詳見我們后面又遇到的一個問題:TCP 長連接 CWND reset 的問題分析。
Initial CWND 如果修改的話也有辦法。
這里的 thread 的討論,有人提出了一種方法:大意是允許讓應(yīng)用程序通過socket參數(shù)來設(shè)置 CWND 的初始值:
setsockopt(fd, IPPROTO_TCP, TCP_CWND, &val, sizeof (val))
——然后就被罵了個狗血淋頭。
Stephen Hemminger 說 IETF TCP 的家伙已經(jīng)覺得 Linux 里面的很多東西會允許不安全的應(yīng)用了。這么做只會證明他們的想法。這個 patch 需要做很多 researech 才考慮。
如果 misuse,比如,應(yīng)用將這個值設(shè)置的很大,那么假設(shè)一種情況:網(wǎng)絡(luò)發(fā)生擁堵了,這時候應(yīng)用不知道網(wǎng)絡(luò)的情況,如果建立連接的話,還是使用一個很大的initcwnd來啟動,會加劇擁堵,情況會原來越壞,永遠(yuǎn)不會自動恢復(fù)。
David Miller 的觀點(diǎn)是,應(yīng)用不可能知道鏈路 (Route) 上的特點(diǎn):
initcwnd是一個路由鏈路上的特點(diǎn),不是 by application 決定的;
只有人才可能清楚整個鏈路的質(zhì)量,所以這個選項只能由人 by route 設(shè)置。
所以現(xiàn)在只能 by route 設(shè)置。
我實(shí)驗了一下,將 cwnd 設(shè)置為 40:
通過 ip route 命令修改
然后在實(shí)驗,可以看到這時候,client 發(fā)送的時候,可以一次發(fā)送更多的數(shù)據(jù)了。
后記
現(xiàn)在看這個原因,如果懂一點(diǎn) TCP,很快就明白其中的原理,很簡單。
但是現(xiàn)實(shí)情況是,監(jiān)控上只能看到 latency 升高了,但是看不出具體是哪一些請求造成的,只知道這個信息的話,那可能的原因就很多了。到這里,發(fā)現(xiàn)問題之后,一般就進(jìn)入了扯皮的階段:中間件的用戶拿著監(jiān)控(而不是具體的請求日志)去找平臺,平臺感覺是網(wǎng)絡(luò)問題,將問題丟給網(wǎng)絡(luò)團(tuán)隊,網(wǎng)絡(luò)團(tuán)隊去檢查他們自己的監(jiān)控,說他們那邊顯示網(wǎng)絡(luò)沒有問題(網(wǎng)絡(luò)層的延遲當(dāng)然沒有問題)。
如果要查到具體原因的話,需要:
先從日志中查找到具體的高延遲的請求。監(jiān)控是用來發(fā)現(xiàn)問題的,而不是用來 debug 的;
從日志分析時間到底花在了哪一個階段;
通過抓包,或者其他手段,驗證步驟2 (這個過程略微復(fù)雜,因為要從眾多連接和數(shù)據(jù)包中找到具體一個 TCP 的數(shù)據(jù)流)
我發(fā)現(xiàn)在大公司里面,這個問題往往牽扯了多個團(tuán)隊,大家在沒有確認(rèn)問題就出現(xiàn)在某一個團(tuán)隊負(fù)責(zé)的范圍內(nèi)的時候,就沒有人去這么查。
我在排查的時候,還得到一些錯誤信息,比如開發(fā)者告訴我 TCP 連接的保持時間是 10min,然后我從日志看,1min 內(nèi)連續(xù)的請求依然會有高延遲的請求,所以就覺得是 TCP 建立連接 overhead 之外的問題。最后抓包才發(fā)現(xiàn)明顯的 SYN 階段包,去和開發(fā)核對邏輯,才發(fā)現(xiàn)所謂的 10min 保持連接,只是在 Server 側(cè)一段做的,Client 側(cè)不關(guān)心這個時間會將 TCP 直接關(guān)掉。
幸好抓到的包不會騙人。
審核編輯:劉清
-
Linux
+關(guān)注
關(guān)注
87文章
11304瀏覽量
209497 -
TCP
+關(guān)注
關(guān)注
8文章
1353瀏覽量
79074 -
RTT
+關(guān)注
關(guān)注
0文章
65瀏覽量
17130
原文標(biāo)題:TCP 擁塞控制對數(shù)據(jù)延遲的影響
文章出處:【微信號:magedu-Linux,微信公眾號:馬哥Linux運(yùn)維】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論