就是要你懂 TCP-- 最经典的TCP性能问题

简介: # 就是要你懂 TCP-- 最经典的TCP性能问题 ### 问题描述 某个PHP服务通过Nginx将后面的tair封装了一下,让其他应用可以通过http协议访问Nginx来get、set 操作tair 上线后测试一切正常,每次操作几毫秒,但是有一次有个应用的value是300K,这个时候set一次需要300毫秒以上。 在没有任何并发压力单线程单次操作也需要这么久,这个延迟是没有道

就是要你懂 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

这就是悲剧的核心原因。

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

这个案例来自: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

相关实践学习
容器服务Serverless版ACK Serverless 快速入门:在线魔方应用部署和监控
通过本实验,您将了解到容器服务Serverless版ACK Serverless 的基本产品能力,即可以实现快速部署一个在线魔方应用,并借助阿里云容器服务成熟的产品生态,实现在线应用的企业级监控,提升应用稳定性。
云原生实践公开课
课程大纲 开篇:如何学习并实践云原生技术 基础篇: 5 步上手 Kubernetes 进阶篇:生产环境下的 K8s 实践 相关的阿里云产品:容器服务&nbsp;ACK 容器服务&nbsp;Kubernetes&nbsp;版(简称&nbsp;ACK)提供高性能可伸缩的容器应用管理能力,支持企业级容器化应用的全生命周期管理。整合阿里云虚拟化、存储、网络和安全能力,打造云端最佳容器化应用运行环境。 了解产品详情:&nbsp;https://www.aliyun.com/product/kubernetes
目录
相关文章
|
网络协议 前端开发
从前端资源的dns/tcp的角度看页面性能
从前端资源的dns/tcp的角度看页面性能
91 0
从前端资源的dns/tcp的角度看页面性能
|
网络协议 Linux 网络安全
系列解读SMC-R:透明无感提升云上 TCP 应用网络性能(一)| 龙蜥技术
已有的应用若想使用RDMA技术改造成本高,那么有没有一种技术是不做任何改造就可以享受RDMA带来的性能优势?
系列解读SMC-R:透明无感提升云上 TCP 应用网络性能(一)| 龙蜥技术
|
缓存 运维 负载均衡
一个支持高网络吞吐量、基于机器性能评分的TCP负载均衡器gobalan
作者最近用golang实现了一个TCP负载均衡器,灵感来自grpc。几个主要的特性就是: - 支持高网络吞吐量 - 实现了基于机器性能评分来分配worker节点的负载均衡算法 - 尽量做到薄客户端,降低客户端复杂性
1437 0
|
SQL 网络协议 关系型数据库
TCP性能和发送接收Buffer的关系
# 前言 本文希望解析清楚,当我们在代码中写下 socket.setSendBufferSize 和 sysctl 看到的rmem/wmem系统参数以及最终我们在TCP常常谈到的接收发送窗口的关系,以及他们怎样影响TCP传输的性能。 先明确一下:**文章标题中所说的Buffer指的是sysctl中的 rmem或者wmem,如果是代码中指定的话对应着SO_SNDBUF或者SO_RCVB
5411 0
|
算法 网络协议 网络性能优化
KBase #11: TCP 拥塞控制算法对网络性能的影响
问题解决状态:[ 已解决 ] 1. 问题所处环境 / Environment 镜像: aliyun-2.1903-x64-20G-alibase-20190327.vhd 及以后所有版本; 内核: kernel-4.19.24-14.al7 及以前所有版本。
1788 0
|
网络协议 测试技术 数据库
|
算法 网络协议 Java
【计算机网络】TCP通信的细节及TCP连接对HTTP事务处理性能影响
从三次握手的细节说起 刚开始尝试使用java等后端语言写IO流,或用套接字(socket)实现简单C/S通信的同学们,常常会接触到的一个概念:就是所谓的“三次握手”,socket作为一个API接口,封装了TCP/IP通信的细节,使我们只需要调用简单的接口而无需关心具体的实现,那么 Socket三...
1017 0
|
网络协议 开发者 安全
|
网络协议
TCP/IP协议的介绍
TCP/IP协议是众多协议的统称,通过分层结构来管理。可分为七层模型或四层结构
|
网络协议 网络架构
六、TCP/IP模型 和 5层参考模型
六、TCP/IP模型 和 5层参考模型
六、TCP/IP模型 和 5层参考模型