2019独角兽企业重金招聘Python工程师标准>>>

最近的业余时间几乎全部献给 breeze 这个多年前挖 下的大坑—— 一个异步 HTTP Server。努力没有白费,项目已经逐渐成型了, 基本的框架已经有了,一个静态 文件模块也已经实现了。

写 HTTP Server,不可免俗地一定要用 ab 跑一下性能,结果一跑不打紧,出现了一个困扰了我好几天的问题:神秘的 40ms 延迟。

Table of Contents

  • 1 现象
  • 2 背后的原因
  • 3 为什么只有 Write-Write-Read 时才会出问题
  • 4 解决方案
    • 4.1 优化协议
    • 4.2 开启TCP_NODELAY

1 现象

现象是这样的,首先看我用 ab 不加 -k 选项的结果:

[~/dev/personal/breeze]$ /usr/sbin/ab  -c 1 -n 10 http://127.0.0.1:8000/styles/shThemeRDark.css
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/Benchmarking 127.0.0.1 (be patient).....doneServer Software:        breeze/0.1.0
Server Hostname:        127.0.0.1
Server Port:            8000Document Path:          /styles/shThemeRDark.css
Document Length:        127 bytesConcurrency Level:      1
Time taken for tests:   0.001 seconds
Complete requests:      10
Failed requests:        0
Write errors:           0
Total transferred:      2700 bytes
HTML transferred:       1270 bytes
Requests per second:    9578.54 [#/sec] (mean)
Time per request:       0.104 [ms] (mean)
Time per request:       0.104 [ms] (mean, across all concurrent requests)
Transfer rate:          2525.59 [Kbytes/sec] receivedConnection Times (ms)min  mean[+/-sd] median   max
Connect:        0    0   0.0      0       0
Processing:     0    0   0.0      0       0
Waiting:        0    0   0.0      0       0
Total:          0    0   0.1      0       0Percentage of the requests served within a certain time (ms)50%      066%      075%      080%      090%      095%      098%      099%      0100%      0 (longest request)

很好,不超过 1ms 的响应时间。但一旦我加上了 -k 选项启用 HTTP Keep-Alive,结果就变成了这样:

[~/dev/personal/breeze]$ /usr/sbin/ab -k  -c 1 -n 10 http://127.0.0.1:8000/styles/shThemeRDark.css
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/Benchmarking 127.0.0.1 (be patient).....doneServer Software:        breeze/0.1.0
Server Hostname:        127.0.0.1
Server Port:            8000Document Path:          /styles/shThemeRDark.css
Document Length:        127 bytesConcurrency Level:      1
Time taken for tests:   0.360 seconds
Complete requests:      10
Failed requests:        0
Write errors:           0
Keep-Alive requests:    10
Total transferred:      2750 bytes
HTML transferred:       1270 bytes
Requests per second:    27.75 [#/sec] (mean)
Time per request:       36.041 [ms] (mean)
Time per request:       36.041 [ms] (mean, across all concurrent requests)
Transfer rate:          7.45 [Kbytes/sec] receivedConnection Times (ms)min  mean[+/-sd] median   max
Connect:        0    0   0.0      0       0
Processing:     1   36  12.4     40      40
Waiting:        0    0   0.2      0       1
Total:          1   36  12.4     40      40Percentage of the requests served within a certain time (ms)50%     4066%     4075%     4080%     4090%     4095%     4098%     4099%     40100%     40 (longest request)

40ms 啊!这可是访问本机上的 Server 啊,才 1 个连接啊!太奇怪了吧!祭出 神器 strace,看看到底是什么情况:

15:37:47.493170 epoll_wait(3, {}, 1024, 0) = 0
15:37:47.493210 readv(5, [{"GET /styles/shThemeRDark.css HTT"..., 10111}, {"GET /styles/shThemeRDark.css HTT"..., 129}], 2) = 129
15:37:47.493244 epoll_wait(3, {}, 1024, 0) = 0
15:37:47.493279 write(5, "HTTP/1.0 200 OK\r\nContent-Type: t"..., 148) = 148
15:37:47.493320 write(5, "<html><head><title>Hello world</"..., 127) = 127
15:37:47.493347 epoll_wait(3, {}, 1024, 0) = 0
15:37:47.493370 readv(5, 0x7fff196a6740, 2) = -1 EAGAIN (Resource temporarily unavailable)
15:37:47.493394 epoll_ctl(3, EPOLL_CTL_MOD, 5, {...}) = 0
15:37:47.493417 epoll_wait(3, {?} 0x7fff196a67a0, 1024, 100) = 1
15:37:47.532898 readv(5, [{"GET /styles/shThemeRDark.css HTT"..., 9982}, {"GET /styles/shThemeRDark.css HTT"..., 258}], 2) = 129
15:37:47.533029 epoll_ctl(3, EPOLL_CTL_MOD, 5, {...}) = 0
15:37:47.533116 write(5, "HTTP/1.0 200 OK\r\nContent-Type: t"..., 148) = 148
15:37:47.533194 write(5, "<html><head><title>Hello world</"..., 127) = 127

发现是读下一个请求之前的那个epoll_wait花了 40ms 才返回。这意味着要 么是 client 等了 40ms 才给我发请求,要么是我上面write写入的数据过 了 40ms 才到达 client。前者的可能性几乎没有,ab 作为一个压力测试工具, 是不可能这样做的,那么问题只有可能是之前写入的 response 过了 40ms 才到 达 client。

2 背后的原因

为什么延迟不高不低正好 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 的 时候才会出现问题。

3 为什么只有 Write-Write-Read 时才会出问题

维基百科上的有一段伪代码来介绍 Nagle’s Algorithm:

if there is new data to sendif the window size >= MSS and available data is >= MSSsend complete MSS segment nowelseif there is unconfirmed data still in the pipeenqueue data in the buffer until an acknowledge is receivedelsesend data immediatelyend ifend 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,从而不会导致任何问题。

我做了一个简单的试验,注释掉了 HTTP Body 的发送,仅仅发送 Headers, Content-Length 指定为 0。这样就不会有第二个 write,变成了 write-read-write-read 模式。此时再用 ab 测试,果然没有 40ms 的延迟了。

说完了问题,该说解决方案了。

4 解决方案

4.1 优化协议

连续 write 小数据包,然后 read 其实是一个不好的网络编程模式,这样的连 续 write 其实应该在应用层合并成一次 write。

可惜的是,我的程序貌似不太好做这样的优化,需要打破一些设计,等我有时间 了再好好调整,至于现在嘛,就很屌丝地用下一个解决方法了。

4.2 开启TCP_NODELAY

简单地说,这个选项的作用就是禁用 Nagle’s Algorithm,禁止后当然就不会有 它引起的一系列问题了。在 UNIX C 里使用setsockopt可以做到:

static void _set_tcp_nodelay(int fd) {int enable = 1;setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (void*)&enable, sizeof(enable));
}

在 Java 里就更简单了,Socket 对象上有一个setTcpNoDelay的方法,直接设 置成true即可。

据我所知,Nginx 默认是开启了这个选项的,这也给了我一点安慰:既然 Nginx 都这么干了,我就先不忙为了这个问题打破设计了,也默认开启TCP_NODELAY吧……

转载于:https://my.oschina.net/u/658658/blog/514312

神秘的40毫秒延迟与 TCP_NODELAY相关推荐

  1. Netflix 工程师的生活 —— 40毫秒的案例

    正文字数:3049  阅读时长:4分钟 并非真正使用了 WebRTC,但此处存在使用 WebRTC 技术性质的相似之处. 作者 / John Blair, Netflix Partner Engine ...

  2. 卡拉赞服务器延迟,魔兽怀旧服:TBC前策划的“小动作”,400毫秒延迟被改!

    魔兽世界怀旧服TBC前策划的"小动作",400毫秒延迟被改!改成了10毫秒,算是大幅度的减少了! 冰环双爆实现不了了吧?这是法师的看家本领,就算改变延迟,法师双爆这个设定也会保留下 ...

  3. fastclick:处理移动端click事件300毫秒延迟

    fastclick:处理移动端click事件300毫秒延迟 1.兼容性 iOS 3及更高版本的移动Safari iOS 5及更高版本的Chrome Android上的Chrome(ICS) Opera ...

  4. ios点击有300毫秒延迟,输入框必须重压或长按才能获取焦点唤起软键盘

    以下方法主要针对在vue中的使用 // ios点击有300毫秒延迟 1. 安装依赖包cnpm install fastclick --save 2. 在 /src/main.js 中引入并使用impo ...

  5. vue.js 使用 fastclick解决移动端click事件300毫秒延迟方法

    一般情况下一些框架就自动解决了这个问题 一.使用npm安装: npm install fastclick -S 二.用法: 安装完以后,可以在在main.js中全局引入,并绑定到body,全局生效.或 ...

  6. 5G在中国一步步满血,高通实现毫米波独立组网:7.1Gbps网速、3.6毫秒延迟

    5G商用三年多,中国也建成了全球最大的5G网络,10月份基站总数就突破200万了,提前实现全年目标,不过5G建设不是一蹴而就的,毫米波5G还在早期阶段,高通日前也在中国实现了5G毫米波的突破. 据高通 ...

  7. 我关注的一周技术动态 2015.10.11

    分布式系统实践 1. Hadoop生态新增列式存储系统Kudu https://mp.weixin.qq.com/s?__biz=MjM5NzAyNTE0Ng==&mid=208082880& ...

  8. 《大咖讲Wireshark网络分析》—再来一个很妖的问题

    本节书摘来自异步社区<大咖讲Wireshark网络分析>一书中的再来一个很妖的问题,作者林沛满,更多章节内容可以访问云栖社区"异步社区"公众号查看. 再来一个很妖的问题 ...

  9. 论文翻译:2021_Low-Delay Speech Enhancement Using Perceptually Motivated Target and Loss

    论文地址:使用感知动机目标和损失的低延迟语音增强 引用格式:Zhang X, Ren X, Zheng X, et al. Low-Delay Speech Enhancement Using Per ...

最新文章

  1. 浏览器常见兼容性问题汇总
  2. confluence添加用户_Confluence 6 选项 1 – 在 Confluence 中手动重建用户和用户组
  3. SSH客户端字符集编码设置
  4. Linux 之十二 Makefile 从入门到放弃全解
  5. MIT新研究:基于基本运动能力让机器人自主学习,感知世界
  6. Javascript 通用Excel导出函数
  7. ssl证书 所属项目怎么上传_Typora + 七牛云图床快速配置,告别手动上传图片!...
  8. C语言面试必问的经典问题(纯”gan“货)
  9. pcm设备相关代码解析
  10. 【推荐系统->统计学】辛普森悖论(Simpson‘s paradox)
  11. XRecyclerView Scrapped or attached views may not be recycled
  12. 推挽 matlab仿真,推挽电路简单介绍
  13. Win7 VNC远程连接Centos桌面
  14. rtx3060性能相当于什么水平 rtx3060参数
  15. 2023年,如何管理你的绩效目标?
  16. 【VSCode报错】 Error while fetching extensions : XHR failed
  17. 利用python实现一个简单的表白程序
  18. Nexus 7 跳过网络验证
  19. 基于MATALB的植物病虫害识别系统设计与实现(已完结)
  20. 一种黑科技:珂朵莉树

热门文章

  1. netty权威指南第三版_Hadoop权威指南(第二版及第三版)
  2. php 怎么复制一个文件,php如何复制文件夹?
  3. React中StrictMode严格模式
  4. mysql 表 区 块 页_数据库 | 001-MySQL梳理系列(一)
  5. 数据集转换_为什么LSTM看起来那么复杂,以及如何避免时序数据处理差异和混乱...
  6. androidstudio自带git用法_Android Studio的git功能的使用介绍
  7. 怎么用python统计字数_Python 统计字数的思路详解
  8. Eclipse中自动创建set、get方法
  9. 安卓选择多张图片上传_微信7.0.5更新!安卓客户端领先ios发布,新增多项实用功能...
  10. python安装失败未指定_windows 7 32bit安装 python3.5.0 安装错误 0x80240017 -未指定错误...