最經(jīng)典的TCP性能問(wèn)題

就是要你懂 TCP-- 最經(jīng)典的TCP性能問(wèn)題

問(wèn)題描述

某個(gè)PHP服務(wù)通過(guò)Nginx將后面的tair封裝了一下,讓其他應(yīng)用可以通過(guò)http協(xié)議訪問(wèn)Nginx來(lái)get、set 操作tair

上線后測(cè)試一切正常,每次操作幾毫秒,但是有一次有個(gè)應(yīng)用的value是300K,這個(gè)時(shí)候set一次需要300毫秒以上。 在沒(méi)有任何并發(fā)壓力單線程單次操作也需要這么久,這個(gè)延遲是沒(méi)有道理和無(wú)法接受的。

問(wèn)題的原因

是因?yàn)門(mén)CP協(xié)議為了做一些帶寬利用率、性能方面的優(yōu)化,而做了一些特殊處理。比如Delay Ack和Nagle算法。

這個(gè)原因?qū)Υ蠹依斫釺CP基本的概念后能在實(shí)戰(zhàn)中了解一些TCP其它方面的性能和影響。

什么是delay ack

由我前面的TCP介紹文章大家都知道,TCP是可靠傳輸,可靠的核心是收到包后回復(fù)一個(gè)ack來(lái)告訴對(duì)方收到了。

來(lái)看一個(gè)例子:


image.png

截圖中的Nignx(8085端口),收到了一個(gè)http request請(qǐng)求,然后立即回復(fù)了一個(gè)ack包給client,接著又回復(fù)了一個(gè)http response 給client。大家注意回復(fù)的ack包長(zhǎng)度66,實(shí)際內(nèi)容長(zhǎng)度為0,ack信息放在TCP包頭里面,也就是這里發(fā)了一個(gè)66字節(jié)的空包給客戶端來(lái)告訴客戶端我收到你的請(qǐng)求了。

這里沒(méi)毛病,邏輯很對(duì),符合TCP的核心可靠傳輸?shù)囊饬x。但是帶來(lái)的一個(gè)問(wèn)題是:帶寬效率不高。那能不能優(yōu)化呢?

這里的優(yōu)化就是delay ack。

delay ack是指收到包后不立即ack,而是等一小會(huì)(比如40毫秒)看看,如果這40毫秒以內(nèi)正好有一個(gè)包(比如上面的http response)發(fā)給client,那么我這個(gè)ack包就跟著發(fā)過(guò)去(順風(fēng)車(chē),http reponse包不需要增加任何大小),這樣節(jié)省了資源。 當(dāng)然如果超過(guò)這個(gè)時(shí)間還沒(méi)有包發(fā)給client(比如nginx處理需要40毫秒以上),那么這個(gè)ack也要發(fā)給client了(即使為空,要不client以為丟包了,又要重發(fā)http request,劃不來(lái))。

假如這個(gè)時(shí)候ack包還在等待延遲發(fā)送的時(shí)候,又收到了client的一個(gè)包,那么這個(gè)時(shí)候server有兩個(gè)ack包要回復(fù),那么os會(huì)把這兩個(gè)ack包合起來(lái)立即回復(fù)一個(gè)ack包給client,告訴client前兩個(gè)包都收到了。

也就是delay ack開(kāi)啟的情況下:ack包有順風(fēng)車(chē)就搭;如果湊兩個(gè)ack包自己包個(gè)車(chē)也立即發(fā)車(chē);再如果等了40毫秒以上也沒(méi)順風(fēng)車(chē),那么自己打個(gè)車(chē)也發(fā)車(chē)。

截圖中Nginx沒(méi)有開(kāi)delay ack,所以你看紅框中的ack是完全可以跟著綠框(http response)一起發(fā)給client的,但是沒(méi)有,紅框的ack立即打車(chē)跑了

什么是Nagle算法

下面的偽代碼就是Nagle算法的基本邏輯,摘自wiki

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

這段代碼的意思是如果要發(fā)送的數(shù)據(jù)大于 MSS的話,立即發(fā)送。
否則:
看看前面發(fā)出去的包是不是還有沒(méi)有ack的,如果有沒(méi)有ack的那么我這個(gè)小包不急著發(fā)送,等前面的ack回來(lái)再發(fā)送

我總結(jié)下Nagle算法邏輯就是:如果發(fā)送的包很小(不足MSS),又有包發(fā)給了對(duì)方對(duì)方還沒(méi)回復(fù)說(shuō)收到了,那我也不急著發(fā),等前面的包回復(fù)收到了再發(fā)。這樣可以優(yōu)化帶寬利用率(早些年帶寬資源還是很寶貴的),Nagle算法也是用來(lái)優(yōu)化改進(jìn)tcp傳輸效率的。

如果client啟用Nagle,并且server端啟用了delay ack會(huì)有什么后果呢?

假如client要發(fā)送一個(gè)http請(qǐng)求給server,這個(gè)請(qǐng)求有1600個(gè)bytes,握手的MSS是1460,那么這1600個(gè)bytes就會(huì)分成2個(gè)TCP包,第一個(gè)包1460,剩下的140bytes放在第二個(gè)包。第一個(gè)包發(fā)出去后,server收到第一個(gè)包,因?yàn)閐elay ack所以沒(méi)有回復(fù)ack,同時(shí)因?yàn)閟erver沒(méi)有收全這個(gè)HTTP請(qǐng)求,所以也沒(méi)法回復(fù)HTTP response(server等一個(gè)完整的HTTP請(qǐng)求,或者40毫秒的delay時(shí)間)。client這邊開(kāi)啟了Nagle算法(默認(rèn)開(kāi)啟)第二個(gè)包比較?。?40<MSS),第一個(gè)包的ack還沒(méi)有回來(lái),那么第二個(gè)包就不發(fā)了,等!互相等!一直到Delay Ack的Delay時(shí)間到了!

這就是悲劇的核心原因。

再來(lái)看一個(gè)經(jīng)典例子和數(shù)據(jù)分析

這個(gè)案例來(lái)自:http://www.stuartcheshire.org/papers/nagledelayedack/

案例核心奇怪的問(wèn)題是,如果傳輸?shù)臄?shù)據(jù)是 99,900 bytes,速度5.2M/秒;
如果傳輸?shù)臄?shù)據(jù)是 100,000 bytes 速度2.7M/秒,多了10個(gè)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個(gè)整包會(huì)立即發(fā)送(都是整包,不受Nagle算法的影響),因?yàn)?8是偶數(shù),對(duì)方收到最后兩個(gè)包后立即回復(fù)ack(delay ack湊夠兩個(gè)也立即ack),那么剩下的1436也很快發(fā)出去(根據(jù)Nagle算法,沒(méi)有沒(méi)ack的包了,立即發(fā))

100,000 bytes:

前面68個(gè)整包很快發(fā)出去也收到ack回復(fù)了,然后發(fā)了第69個(gè)整包,剩下88bytes(不夠一個(gè)整包)根據(jù)Nagle算法要等一等,server收到第69個(gè)ack后,因?yàn)閐elay ack不回復(fù)(手里只攢下一個(gè)沒(méi)有回復(fù)的包),所以client、server兩邊等在等,一直等到server的delay ack超時(shí)了。

挺奇怪和挺有意思吧,作者還給出了傳輸數(shù)據(jù)的圖表:

這是有問(wèn)題的傳輸圖,明顯有個(gè)平臺(tái)層,這個(gè)平臺(tái)層就是兩邊在互相等,整個(gè)速度肯定就上不去。

如果傳輸?shù)亩际?9,900,那么整個(gè)圖形就很平整:

回到前面的問(wèn)題

服務(wù)寫(xiě)好后,開(kāi)始測(cè)試都沒(méi)有問(wèn)題,rt很正常(一般測(cè)試的都是小對(duì)象),沒(méi)有觸發(fā)這個(gè)問(wèn)題。后來(lái)碰到一個(gè)300K的rt就到幾百毫秒了,就是因?yàn)檫@個(gè)原因。

另外有些http post會(huì)故意把包頭和包內(nèi)容分成兩個(gè)包,再加一個(gè)Expect參數(shù)之類(lèi)的,更容易觸發(fā)這個(gè)問(wèn)題。

這是修改后的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))                
            ) {

            //這里如果是小包就不開(kāi)delay ack,實(shí)際不科學(xué)
            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);
            }

上面中文注釋的部分是后來(lái)的改進(jìn),然后經(jīng)過(guò)測(cè)試同一個(gè)300K的對(duì)象也能在幾毫米以內(nèi)完成get、set了。

尤其是在Post請(qǐng)求將HTTP Header和Body內(nèi)容分成兩個(gè)包后,容易出現(xiàn)這種延遲問(wèn)題


就是要你懂TCP相關(guān)文章:

關(guān)于TCP 半連接隊(duì)列和全連接隊(duì)列
MSS和MTU導(dǎo)致的悲劇
2016年雙11通過(guò)網(wǎng)絡(luò)優(yōu)化提升10倍性能
就是要你懂TCP的握手和揮手


總結(jié)

這個(gè)問(wèn)題確實(shí)經(jīng)典,非常隱晦一般不容易碰到,碰到一次決不放過(guò)她。文中所有client、server的概念都是相對(duì)的,client也有delay ack的問(wèn)題。 Nagle算法一般默認(rèn)開(kāi)啟的

參考文章:
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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,247評(píng)論 6 543
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,520評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 178,362評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,805評(píng)論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,541評(píng)論 6 412
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,896評(píng)論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,887評(píng)論 3 447
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 43,062評(píng)論 0 290
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,608評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,356評(píng)論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,555評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,077評(píng)論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,769評(píng)論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 35,175評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 36,489評(píng)論 1 295
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,289評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,516評(píng)論 2 379

推薦閱讀更多精彩內(nèi)容