网络40ms延迟问题

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS MySQL,高可用系列 2核4GB
简介:

问题背景:
我 们一个企业用户准备把线上业务从共享的mysql服务迁移到独立型mysql rds上。企业用户那边先搞了一个test版本到我们rds环境,发现网站响应时间从3s变为40s。由于是php应用,故我们让用户应用开启  xhprof调试后,看一次请求有1200多次mysql查询,对mysql的查询量非常大,并且请求的时间90%以上都花在了mysql上。而用户使用 共享性mysql和使用rds的区别,是用户runtime到rds之间多了一个proxy服务,通过proxy服务把用户请求代理到了rds。

问题跟踪:
1,由于用户使用共享mysql和rds的区别,就是一个proxy服务。故就把问题定位在proxy这个服务上。
2,使用tcpdump在proxy机器(15.212)上抓runtime(21.102)到proxy,proxy到rds(144.139)的数据包,具体如下:

wKiom1b8kKHw5eZCAAKKET3TZf0213.png

proxy收到runtime发过来的查询数据包到proxy给runtime ack确认,花费了40ms,这个时间太长。并且,proxy把runtime发的查询数据包从一个截断为了2个数据包。  
1,id为22953:runtime102发送select语句到proxy 212。数据包长度为296byte 时间:23.877515
2,id为22954:proxy 212发送了一部分select语句128byte到rds 139。时间花销(秒):23.877611-23.877515=0.000096
3,id为22955:proxy 212回 runtime 103的ack这一步时间花销(秒):23.917294-23.877611=0.039683(这次时间花销过大,一千次请求就是39s
4,id为22956:rds 139回proxy  212的ack。这一步时间花销(秒):23.918398-23.917294=0.001104
5,id为22957:proxy 212发送剩余部分select语句168byte到rds  139。这一步时间花销(秒):23.918415-23.918398=0.000017

3,又在proxy机器上进行了strace跟踪,结果如下:

1
2
3
4
5
6
7
8
9
11:00:57.923865 epoll_wait(7, {{EPOLLIN, {u32=12025792, u64=12025792}}}, 1024, 500) = 1
11:00:57.923964 recvfrom(8,  "\237\0\0\0\3SELECT cat_id, cat_name, parent_id, is_show FROM `jiewang300`.`jw_category`WHERE parent_id = '628' AND is_show = 1 ORDER BY" , 128, 0, NULL, NULL) = 128
11:00:57.924041 recvfrom(8,  " sort_order ASC, cat_id ASC limit 8" , 128, 0, NULL, NULL) = 35
11:00:57.924102 recvfrom(8, 0xb7b0b3, 93, 0, 0, 0) = -1 EAGAIN (Resource temporarily unavailable)
11:00:57.924214 epoll_wait(7, {{EPOLLOUT, {u32=12048160, u64=12048160}}}, 1024, 500) = 1
11:00:57.924340 sendto(9,  "\237\0\0\0\3SELECT cat_id, cat_name, parent_id, is_show FROM `jiewang300`.`jw_category`WHERE parent_id = '628' AND is_show = 1 ORDER BY" , 128, 0, NULL, 0) = 128
11:00:57.924487 sendto(9,  " sort_order ASC, cat_id ASC limit 8" , 35, 0, NULL, 0) = 35
11:00:57.924681 epoll_wait(7, {{EPOLLIN, {u32=12048160, u64=12048160}}}, 1024, 500) = 1
11:00:57.964162 recvfrom(9,  "\1\0\0\1\4B\0\0\2\3def\njiewang300\vjw_category\vjw_category\6cat_id\6cat_id\f?\0\5\0\0\0\2#B\0\0\0F\0\0\3\3def\njiewang300\vjw_category\vjw_category\10cat_name\10" , 128, 0, NULL, NULL) = 128

发现是proxy机器在write write read操作的时候,write write和read操作之间,epoll_wait了40ms才read到数据。

结论:
为什么延迟不高不低正好 40ms 呢?果断 Google 一下找到了答案。原来这是 TCP 协议中的 Nagle‘s Algorithm 和 TCP Delayed Acknoledgement 共同起作 用所造成的结果。
 
Nagle’s Algorithm 是为了提高带宽利用率设计的算法,其做法是合并小的TCP 包为一个,避免了过多的小报文的 TCP 头所浪费的带宽。如果开启了这个算法 (默认),则协议栈会累积数据直到以下两个条件之一满足的时候才真正发送出去:
1,积累的数据量到达最大的 TCP Segment Size
2,收到了一个 Ack
 
TCP Delayed Acknoledgement 也是为了类似的目的被设计出来的,它的作用就 是延迟 Ack 包的发送,使得协议栈有机会合并多个 Ack,提高网络性能。

如果一个 TCP 连接的一端启用了 Nagle‘s Algorithm,而另一端启用了 TCP Delayed Ack,而发送的数据包又比较小,则可能会出现这样的情况:发送端在等 待接收端对上一个packet 的 Ack 才发送当前的 packet,而接收端则正好延迟了 此 Ack 的发送,那么这个正要被发送的 packet 就会同样被延迟。当然 Delayed Ack 是有个超时机制的,而默认的超时正好就是 40ms。
 
现代的 TCP/IP 协议栈实现,默认几乎都启用了这两个功能,你可能会想,按我 上面的说法,当协议报文很小的时候,岂不每次都会触发这个延迟问题?事实不 是那样的。仅当协议的交互是发送端连续发送两个 packet,然后立刻 read 的 时候才会出现问题。
 
为什么只有 Write-Write-Read 时才会出问题,维基百科上的有一段伪代码来介绍 Nagle’s Algorithm:

1
2
3
4
5
6
7
8
9
10
11
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 小的时候(外层的 else 分支),还要再判断 时候还有未确认的数据。只有当管道里还有未确认数据的时候才会进入缓冲区, 等待 Ack。

所以发送端发送的第一个 write 是不会被缓冲起来,而是立刻发送的(进入内层 的else 分支),这时接收端收到对应的数据,但它还期待更多数据才进行处理, 所以不会往回发送数据,因此也没机会把 Ack 给带回去,根据Delayed Ack 机制, 这个 Ack 会被 Hold 住。这时发送端发送第二个包,而队列里还有未确认的数据 包,所以进入了内层 if 的 then 分支,这个 packet 会被缓冲起来。此时,发 送端在等待接收端的 Ack;接收端则在 Delay 这个 Ack,所以都在等待,直到接 收端 Deplayed Ack 超时(40ms),此 Ack 被发送回去,发送端缓冲的这个 packet 才会被真正送到接收端,从而继续下去。

再看我上面的 strace 记录也能发现端倪,因为设计的一些不足,我没能做到把 短小的 HTTP Body 连同 HTTP Headers 一起发送出去,而是分开成两次调用实 现的,之后进入 epoll_wait 等待下一个 Request 被发送过来(相当于阻塞模 型里直接 read)。正好是 write-write-read 的模式。

那么 write-read-write-read 会不会出问题呢?维基百科上的解释是不会:
    “The user-level solution is to avoid write-write-read sequences on sockets. write-read-write-read is fine. write-write-write is fine. But write-write-read is a killer. So, if you can, buffer up your little writes to TCP and send them all at once. Using the standard UNIX I/O package and flushing write before each read usually works.”
我的理解是这样的:因为第一个 write 不会被缓冲,会立刻到达接收端,如果是 write-read-write-read 模式,此时接收端应该已经得到所有需要的数据以进行 下一步处理。接收端此时处理完后发送结果,同时也就可以把上一个packet 的 Ack 可以和数据一起发送回去,不需要 delay,从而不会导致任何问题。


解决办法:
在proxy服务上开启 TCP_NODELAY选项,这个选项的作用就是禁用 Nagle’s Algorithm。


plus:

tcpdump和strace结果对比:

可以看到strace结果中是proxy机器先write write两次,然后等待大概40ms。而tcpdump看到的则是proxy机器先write了一次到rds,等待40ms收到rds的ack,再write了剩余的数据到rds。strace这个结果是正常的,因为strace只能看到的是系统调用,实际上第二次write被缓冲了(strace是看不到的),等待了40ms后rds的ack到来,这第二次write才被真正的发送出去。这样strace就和tcpdump的结果一致了。










本文转自 leejia1989 51CTO博客,原文链接:http://blog.51cto.com/leejia/1758560,如需转载请自行联系原作者
相关实践学习
通过Ingress进行灰度发布
本场景您将运行一个简单的应用,部署一个新的应用用于新的发布,并通过Ingress能力实现灰度发布。
容器应用与集群管理
欢迎来到《容器应用与集群管理》课程,本课程是“云原生容器Clouder认证“系列中的第二阶段。课程将向您介绍与容器集群相关的概念和技术,这些概念和技术可以帮助您了解阿里云容器服务ACK/ACK Serverless的使用。同时,本课程也会向您介绍可以采取的工具、方法和可操作步骤,以帮助您了解如何基于容器服务ACK Serverless构建和管理企业级应用。 学习完本课程后,您将能够: 掌握容器集群、容器编排的基本概念 掌握Kubernetes的基础概念及核心思想 掌握阿里云容器服务ACK/ACK Serverless概念及使用方法 基于容器服务ACK Serverless搭建和管理企业级网站应用
目录
相关文章
|
3月前
|
分布式计算 监控 网络协议
Hadoop集群长时间运行网络延迟原因
【6月更文挑战第20天】
72 2
|
4月前
网络延迟导致终端卡顿
网络延迟导致终端卡顿
|
弹性计算 Linux 测试技术
阿里云ECS网络不稳定、访问丢包、延迟高怎么办?
若ECS服务器经常出现网络不稳定、延迟高等情况,针对不同情况,下面列出一些常用的解决方法供大家参考: 一、Linux实例 可以尝试先用如winmtr之类的工具,查看是服务端的丢包还是网际路由线路的丢包。
|
4月前
|
缓存 负载均衡 监控
中间件网络延迟
中间件网络延迟
33 2
|
4月前
|
存储 网络协议 数据中心
|
存储 网络协议 容灾
降低存储网络55% 延迟!阿里云存储论文入选计算机顶会
凭借在规模化部署和应用模型上的创新,阿里云存储团队发表的技术论文《Deploying User-space TCP at Cloud Scale with LUNA》被 USENIX ATC'23 收录。
1243 4
降低存储网络55% 延迟!阿里云存储论文入选计算机顶会
|
4月前
|
存储 网络协议 网络性能优化
网络传输延迟
网络传输延迟
52 1
|
4月前
|
存储 并行计算 网络协议
|
4月前
|
网络协议 网络安全 区块链
常见网络延迟测量方法
常见网络延迟测量方法
357 0
|
9月前
|
Kubernetes 物联网 Perl
Chaos Mesh网络延迟原理探索
在使用Chaos Mesh的过程中发现, 注入网络类故障可以做到对pod的无感知注入,好奇是如何实现的。 所以这里对网络故障注入进行简单的探索,以便更深层的了解故障注入。
下一篇
云函数