就是要你懂TCP–最經典的TCP效能問題
就是要你懂 TCP– 最經典的TCP效能問題
問題描述
某個PHP服務通過Nginx將後面的tair封裝了一下,讓其他應用可以通過http協議訪問Nginx來get、set 操作tair
上線後測試一切正常,每次操作幾毫秒,但是有一次有個應用的value是300K,這個時候set一次需要300毫秒以上。 在沒有任何併發壓力單執行緒單次操作也需要這麼久,這個延遲是沒有道理和無法接受的。
問題的原因
是因為TCP協議為了做一些頻寬利用率、效能方面的優化,而做了一些特殊處理。比如Delay Ack和Nagle演算法。
這個原因對大家理解TCP基本的概念後能在實戰中瞭解一些TCP其它方面的效能和影響。
什麼是delay ack
由我前面的TCP介紹文章大家都知道,TCP是可靠傳輸,可靠的核心是收到包後回覆一個ack來告訴對方收到了。
來看一個例子:
截圖中的Nignx(8085埠),收到了一個http request請求,然後立即回覆了一個ack包給client,接著又回覆了一個http response 給client。大家注意回覆的ack包長度66,實際內容長度為0,ack資訊放在TCP包頭裡面,也就是這裡發了一個66位元組的空包給客戶端來告訴客戶端我收到你的請求了。
這裡沒毛病,邏輯很對,符合TCP的核心可靠傳輸的意義。但是帶來的一個問題是:頻寬效率不高。那能不能優化呢?
這裡的優化就是delay ack。
delay ack是指收到包後不立即ack,而是等一小會(比如40毫秒)看看,如果這40毫秒以內正好有一個包(比如上面的http response)發給client,那麼我這個ack包就跟著發過去(順風車,http reponse包不需要增加任何大小),這樣節省了資源。 當然如果超過這個時間還沒有包發給client(比如nginx處理需要40毫秒以上),那麼這個ack也要發給client了(即使為空,要不client以為丟包了,又要重發http request,划不來)。
假如這個時候ack包還在等待延遲傳送的時候,又收到了client的一個包,那麼這個時候server有兩個ack包要回復,那麼os會把這兩個ack包合起來立即回覆一個ack包給client,告訴client前兩個包都收到了。
也就是delay ack開啟的情況下:ack包有順風車就搭;如果湊兩個ack包自己包個車也立即發車;再如果等了40毫秒以上也沒順風車,那麼自己打個車也發車。
截圖中Nginx沒有開delay ack,所以你看紅框中的ack是完全可以跟著綠框(http response)一起發給client的,但是沒有,紅框的ack立即叫車跑了
什麼是Nagle演算法
if there is new data to send
if the window size >= MSS and available data is >= MSS
send complete MSS segment now
else
if there is unconfirmed data still in the pipe
enqueue data in the buffer until an acknowledge is received
else
send data immediately
end if
end if
end if
這段程式碼的意思是如果要傳送的資料大於 MSS的話,立即傳送。
否則:
看看前面發出去的包是不是還有沒有ack的,如果有沒有ack的那麼我這個小包不急著傳送,等前面的ack回來再傳送
我總結下Nagle演算法邏輯就是:如果傳送的包很小(不足MSS),又有包發給了對方對方還沒回復說收到了,那我也不急著發,等前面的包回覆收到了再發。這樣可以優化頻寬利用率(早些年頻寬資源還是很寶貴的),Nagle演算法也是用來優化改進tcp傳輸效率的。
如果client啟用Nagle,並且server端啟用了delay ack會有什麼後果呢?
假如client要傳送一個http請求給server,這個請求有1600個bytes,握手的MSS是1460,那麼這1600個bytes就會分成2個TCP包,第一個包1460,剩下的140bytes放在第二個包。第一個包發出去後,server收到第一個包,因為delay ack所以沒有回覆ack,同時因為server沒有收全這個HTTP請求,所以也沒法回覆HTTP response(server等一個完整的HTTP請求,或者40毫秒的delay時間)。client這邊開啟了Nagle演算法(預設開啟)第二個包比較小(140
這就是悲劇的核心原因。
再來看一個經典例子和資料分析
這個案例來自:http://www.stuartcheshire.org/papers/nagledelayedack/
案例核心奇怪的問題是,如果傳輸的資料是 99,900 bytes,速度5.2M/秒;
如果傳輸的資料是 100,000 bytes 速度2.7M/秒,多了10個bytes,不至於傳輸速度差這麼多。
原因就是:
99,900 bytes = 68 full-sized 1448-byte packets, plus 1436 bytes extra
100,000 bytes = 69 full-sized 1448-byte packets, plus 88 bytes extra
99,900 bytes:
68個整包會立即傳送,因為68是偶數,對方收到最後兩個包後立即回覆ack(delay ack湊夠兩個也立即ack),那麼剩下的1436也很快發出去(根據nagle演算法,沒有沒ack的包了,立即發)
100,000 bytes:
前面68個整包很快發出去也收到ack回覆了,然後發了第69個整包,剩下88bytes根據nagle演算法要等一等,server收到第69個ack後,因為delay ack不回覆(手裡只攢下一個沒有回覆的包),所以client、server兩邊等在等,一直等到server的delay ack超時了。
挺奇怪和挺有意思吧,作者還給出了傳輸資料的圖表:
這是有問題的傳輸圖,明顯有個平臺層,這個平臺層就是兩邊在互相等,整個速度肯定就上不去。
如果傳輸的都是99,900,那麼整個圖形就很平整:
回到前面的問題
服務寫好後,開始測試都沒有問題,rt很正常(一般測試的都是小物件),沒有觸發這個問題。後來碰到一個300K的rt就到幾百毫秒了,就是因為這個原因。
另外有些http post會故意把包頭和包內容分成兩個包,再加一個Expect引數之類的,更容易觸發這個問題。
這是修改後的C程式碼
struct curl_slist *list = NULL;
//合併post包
list = curl_slist_append(list, "Expect:");
CURLcode code(CURLE_FAILED_INIT);
if (CURLE_OK == (code = curl_easy_setopt(curl, CURLOPT_URL, oss.str().c_str())) &&
CURLE_OK == (code = curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, timeout)) &&
CURLE_OK == (code = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &write_callback)) &&
CURLE_OK == (code = curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L)) &&
CURLE_OK == (code = curl_easy_setopt(curl, CURLOPT_POST, 1L)) &&
CURLE_OK == (code = curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, pooh.sizeleft)) &&
CURLE_OK == (code = curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_callback)) &&
CURLE_OK == (code = curl_easy_setopt(curl, CURLOPT_READDATA, &pooh)) &&
CURLE_OK == (code = curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L)) && //1000 ms curl bug
CURLE_OK == (code = curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list))
) {
//這裡如果是小包就不開delay ack,實際不科學
if (request.size() < 1024) {
code = curl_easy_setopt(curl, CURLOPT_TCP_NODELAY, 1L);
} else {
code = curl_easy_setopt(curl, CURLOPT_TCP_NODELAY, 0L);
}
if(CURLE_OK == code) {
code = curl_easy_perform(curl);
}
上面中文註釋的部分是後來的改進,然後經過測試同一個300K的物件也能在幾毫米以內完成get、set了。
尤其是在Post請求將HTTP Header和Body內容分成兩個包後,容易出現這種延遲問題
就是要你懂TCP相關文章:
關於TCP 半連線佇列和全連線佇列
MSS和MTU導致的悲劇
2016年雙11通過網路優化提升10倍效能
就是要你懂TCP的握手和揮手
總結
這個問題確實經典,非常隱晦一般不容易碰到,碰到一次決不放過她。文中所有client、server的概念都是相對的,client也有delay ack的問題。 Nagle演算法一般預設開啟的
參考文章:
https://access.redhat.com/solutions/407743
http://www.stuartcheshire.org/papers/nagledelayedack/
https://en.wikipedia.org/wiki/Nagle%27s_algorithm
https://en.wikipedia.org/wiki/TCP_delayed_acknowledgment
相關文章
- TCP通訊之經典問題解決TCP
- 就是要你懂Spring-IOCSpring
- 為什麼 TCP 協議有效能問題TCP協議
- [Linux]經典面試題 - 網路基礎 - TCP三次握手Linux面試題TCP
- Go TCP 粘包問題GoTCP
- TCP|你真的懂 HTTP 嗎?TCPHTTP
- 圖解 | 原來這就是TCP圖解TCP
- Web 效能之 TCPWebTCP
- TCP粘包拆包問題TCP
- TCP常見問題總結TCP
- TCP協議圖文秒懂TCP協議
- 就是要你懂Java中volatile關鍵字實現原理Java
- TCP/IP 協議相關的問題TCP協議
- tcp 埠的time_wait 問題TCPAI
- TCP/UDP的埠Dynamic Port Range問題TCPUDP
- 第七篇:幾個經典的TCP通訊函式TCP函式
- tcp 實現簡單http 問題TCPHTTP
- TCP/IP傳輸層,你懂多少?TCP
- 成為高手前必懂的TCP乾貨TCP
- 第二篇:一個經典的比喻( 關於TCP連線API )TCPAPI
- TCP 效能優化淺析TCP優化
- TCP協議粘包問題詳解TCP協議
- 1 Million TCP Connection 問題解決TCP
- 動態規劃經典問題----最長公共子序列動態規劃
- 【T07】不要低估tcp的效能TCP
- 一看就懂的TCP握手和揮手TCP
- BATJ大廠測試人員必知的經典效能問題BAT
- Jmeter TCP協議效能測試JMeterTCP協議
- 揹包問題的一道經典問題
- 八數碼 經典問題
- TCP 粘包 - 拆包問題及解決方案TCP
- 『開源』大半夜除錯TCP延遲問題除錯TCP
- 詳說TCP重傳問題的排查思路與實踐TCP
- CAsyncSocket TCP協議通訊速度慢的問題(轉)TCP協議
- 關於 TCP/IP,必知必會的十個問題TCP
- TCP TCP/IP HTTP HTTPSTCPHTTP
- 前端必須懂的計算機網路知識—(TCP)前端計算機網路TCP
- TCP/IP之TCP的建立與終止TCP