最经典的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来告诉对方收到了。

来看一个例子:


image.png

截图中的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算法

下面的伪代码就是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

这段代码的意思是如果要发送的数据大于 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<MSS),第一个包的ack还没有回来,那么第二个包就不发了,等!互相等!一直到Delay Ack的Delay时间到了!

这就是悲剧的核心原因。

再来看一个经典例子和数据分析

这个案例来自: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个整包会立即发送(都是整包,不受Nagle算法的影响),因为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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容