最经典的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算法
下面的伪代码就是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内容分成两个包后,容易出现这种延迟问题。
总结
这个问题确实经典,非常隐晦一般不容易碰到,碰到一次决不放过她。文中所有client、server的概念都是相对的,client也有delay ack的问题。 Nagle算法一般默认开启的
最经典的TCP性能问题的更多相关文章
- 理论经典:TCP协议的3次握手与4次挥手过程详解
1.前言 尽管TCP和UDP都使用相同的网络层(IP),TCP却向应用层提供与UDP完全不同的服务.TCP提供一种面向连接的.可靠的字节流服务. 面向连接意味着两个使用TCP的应用(通常是一个客户和一 ...
- [转载] 高流量大并发Linux TCP 性能调优
原文: http://cenwj.com/2015/2/25/19 本文参考文章为: 优化Linux下的内核TCP参数来提高服务器负载能力 Linux Tuning 本文所面对的情况为: 高并发数 高 ...
- 对TCP性能的考虑
#xiaodeng #对TCP性能的考虑 #HTTP权威指南 86 #对TCP性能的考虑 #HTTP紧挨着TCP,位于其上层.所以HTTP事务的性能很大程度上取决于底层tcp通道的性能. #4.2.1 ...
- [心平气和读经典]The TCP/IP Guide(003)
The TCP/IP Guide [Page 43, 44] Scope of The TCP/IP Guide | 本书的讨论范围 The first step to dealing with a ...
- 《HTTP权威指南》之HTTP连接管理及对TCP性能的考虑
在上一篇博客中(<HTTP权威指南>之HTTP相关概念详解)我们简单对HTTP相关的基本概念做了一些简单的了解,但未对HTTP连接管理的内容做一些详细的介绍.本篇博客我们就一起来看一下HT ...
- (经典)tcp粘包分析
转载自csdn:http://blog.csdn.net/zhangxinrun/article/details/6721495 这两天看csdn有一些关于socket粘包,socket缓冲区设置的问 ...
- linux下改动内核參数进行Tcp性能调优 -- 高并发
前言: Tcp/ip协议对网络编程的重要性,进行过网络开发的人员都知道,我们所编写的网络程序除了硬件,结构等限制,通过改动Tcp/ip内核參数也能得到非常大的性能提升, 以下就列举一些Tcp/ip内核 ...
- 第七篇:几个经典的TCP通信函数
前言 在TCP通信中要使用到几个非常经典的函数,本文将对这几个函数进行一个简短的使用说明. socket()函数 函数作用:创建一个网际字节流套接字 包含头文件:sys/socket.h ( 后面几个 ...
- 几个经典的TCP通信函数
前言 在TCP通信中要使用到几个非常经典的函数( 点这里参考一个关于它们作用的形象比方 ),本文将对这几个函数进行一个简短的使用说明. socket函数 函数作用:创建一个网际字节流套接字 包含头文件 ...
- 【转】TCP性能优化之避免慢启动
TCP协议中有个慢启动,在<TCP/IP详解卷一>中占据的篇幅很小,但是这个东西,在某些业务场景下,对性能的影响非常大. 什么是慢启动 最初的TCP的实现方式是,在连接建立成功后便会向网络 ...
随机推荐
- 下载 ingres-nginx
https://quay.io/repository/kubernetes-ingress-controller/nginx-ingress-controller-arm64?tag=latest&a ...
- vs不同版本支持的c++版本和PlatformToolset,及在vs中切换c++版本
找c++资料从网上找确实更快速,但要想深入地理解vc++建议看msdn文档. vs不同版本支持的c++版本 C++17: vs2017基本支持,vs2015部分支持. C++14: vs2017就可以 ...
- statsvn只支持到svn1.3
怎样找出svn修改次数最多的文件? 我想统计配置表中,那个配置文件修改次数最多,但经过实践发现statsvn只支持到1.3的版本. 通过svn的命令行接口,把提交记录保存到xml中,再通过自己写代码解 ...
- npm查看插件所有版本命令
npm view webpack versions npm view webpack versions
- 希捷推出IronWolf Pro 24TB硬盘:CMR构建、285MB/s传输速率
希捷推出了全新IronWolf Pro 24TB硬盘,采用3.5寸规格,满足中小企业和NAS环境需求. 据了解,新款硬盘采用了希捷AgileArray技术,针对NAS系统进行了优化.其通过双平面平衡和 ...
- U390630 分考场题解
题目链接:U390630 分考场 本题来自于2019年蓝桥杯国赛的题.在洛谷上也被标为了假题.原因是首先官方在需要输出浮点数的情况下,并没有开启spj,并且官方所给的数据当中,总有一两个数据以不知道到 ...
- P8078 [WC2022] 秃子酋长题解
题目链接: P8078 [WC2022] 秃子酋长 题目所求较难理解,我们考虑转化下,首先这是个 \(1 \sim n\) 的排列,而且要求相邻对应的原位置的绝对值最大我们先考虑最简单的一种情况:\( ...
- JVM(Java虚拟机)整理(二)
前言 上一篇内容:JVM(Java虚拟机)整理(一)https://www.cnblogs.com/xiegongzi/p/17994659 Java 内存模型(JMM) Java 内存模型引入 声明 ...
- 一个关于用netty的小错误反思
一个关于用netty的小认知 在使用netty时,观看了黑马的netty网课,没想就直接用他的依赖了 依赖如下 <dependency> <groupId>io.netty&l ...
- ASP.NET Core分布式项目实战(课程介绍,MVP,瀑布与敏捷)--学习笔记
任务1:课程介绍 课程目标: 1.进一步理解 ASP.NET Core 授权认证框架.MVC 管道 2.掌握 Oauth2,结合 Identity Sercer4 实现 OAuth2 和 OpenID ...
