网络40ms延迟问题

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介:

问题背景:
我 们一个企业用户准备把线上业务从共享的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,如需转载请自行联系原作者
相关实践学习
容器服务Serverless版ACK Serverless 快速入门:在线魔方应用部署和监控
通过本实验,您将了解到容器服务Serverless版ACK Serverless 的基本产品能力,即可以实现快速部署一个在线魔方应用,并借助阿里云容器服务成熟的产品生态,实现在线应用的企业级监控,提升应用稳定性。
云原生实践公开课
课程大纲 开篇:如何学习并实践云原生技术 基础篇: 5 步上手 Kubernetes 进阶篇:生产环境下的 K8s 实践 相关的阿里云产品:容器服务 ACK 容器服务 Kubernetes 版(简称 ACK)提供高性能可伸缩的容器应用管理能力,支持企业级容器化应用的全生命周期管理。整合阿里云虚拟化、存储、网络和安全能力,打造云端最佳容器化应用运行环境。 了解产品详情: https://www.aliyun.com/product/kubernetes
目录
相关文章
|
1月前
网络延迟导致终端卡顿
网络延迟导致终端卡顿
|
15天前
|
缓存 负载均衡 监控
中间件网络延迟
中间件网络延迟
23 2
|
2月前
|
存储 网络协议 网络性能优化
网络传输延迟
网络传输延迟
23 1
|
弹性计算 Linux 测试技术
阿里云ECS网络不稳定、访问丢包、延迟高怎么办?
若ECS服务器经常出现网络不稳定、延迟高等情况,针对不同情况,下面列出一些常用的解决方法供大家参考: 一、Linux实例 可以尝试先用如winmtr之类的工具,查看是服务端的丢包还是网际路由线路的丢包。
|
3月前
|
存储 并行计算 网络协议
|
3月前
|
存储 网络协议 数据中心
|
4月前
|
网络协议 网络安全 区块链
常见网络延迟测量方法
常见网络延迟测量方法
263 0
|
10月前
|
存储 网络协议 容灾
降低存储网络55% 延迟!阿里云存储论文入选计算机顶会
凭借在规模化部署和应用模型上的创新,阿里云存储团队发表的技术论文《Deploying User-space TCP at Cloud Scale with LUNA》被 USENIX ATC'23 收录。
1160 4
降低存储网络55% 延迟!阿里云存储论文入选计算机顶会
|
5月前
|
监控 物联网 测试技术
Chaos Mesh网络延迟原理
Chaos Mesh网络延迟原理
80 0
|
5月前
|
Kubernetes 物联网 Perl
Chaos Mesh网络延迟原理探索
在使用Chaos Mesh的过程中发现, 注入网络类故障可以做到对pod的无感知注入,好奇是如何实现的。 所以这里对网络故障注入进行简单的探索,以便更深层的了解故障注入。