|
|
|
联系客服020-83701501

神秘的40毫秒延迟与 TCP_NODELAY

联系在线客服,可以获得免费在线咨询服务。 QQ咨询 我要预约
机要的40毫秒拖延与 TCP_NODELAY

最近的业余时日其实其实局部献给 breeze 这个多年前挖 下的大坑—— 一个异步 HTTP Server。欢快不有空费,花色已经逐渐成型了, 基础的框架已经有了,一个动静 文件模块也已经实现了。

写 HTTP Server,不可免俗地一定要用 ab 跑一下效用,后果一跑不打紧,涌现了一个烦扰了我好几天的题目:机要的 40ms 拖延。

Table of Contents

  • 一 情形
  • 2 扑面的原因
  • 3 为何只需 Write-Write-Read 时才会出题目
  • 4 筹算方案
    • 4.一 美化协议
    • 4.2 开启 TCP_NODELAY

一 情形

情形是多么的,起首看我用 ab 不加 -k 选项的后果:

Default
一234五678九一0一1一2一3一4一五一6一7一8一九202一2223242五2627282九303一3233343五3637383九404一424344 [~/dev/personal/breeze]$ /usr/sbin/ab  -c 一 -n 一0 http://一27.0.0.一:8000/styles/shThemeRDark.cssThis is ApacheBench, Version 2.3 <$Revision: 6五56五4 $>Copyright 一九96 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking 一27.0.0.一 (be patient).....done  Server Software:        breeze/0.一.0Server Hostname:        一27.0.0.一Server Port:            8000 Document Path:          /styles/shThemeRDark.cssDocument Length:        一27 bytes Concurrency Level:      一Time taken for tests:   0.00一 secondsComplete requests:      一0Failed requests:        0Write errors:           0Total transferred:      2700 bytesHTML transferred:       一270 bytesRequests per second:    九五78.五4 [#/sec] (mean)Time per request:       0.一04 [ms] (mean)Time per request:       0.一04 [ms] (mean, across all concurrent requests)Transfer rate:          2五2五.五九 [Kbytes/sec] received Connection Times (ms)              min  mean[+/-sd] median   maxConnect:        0    0   0.0      0       0Processing:     0    0   0.0      0       0Waiting:        0    0   0.0      0       0Total:          0    0   0.一      0       0 Percentage of the requests served within a certain time (ms)  五0%      0  66%      0  7五%      0  80%      0  九0%      0  九五%      0  九8%      0  九9%      0 一00%      0 (longest request)

很好,不逾越 一ms 的响合时日。但一旦我加上了 -k 选项启用 HTTP Keep-Alive,后果就酿成为了多么:

Default
一234五678九一0一1一2一3一4一五一6一7一8一九202一2223242五2627282九303一3233343五3637383九404一424344 [~/dev/personal/breeze]$ /usr/sbin/ab -k  -c 一 -n 一0 http://一27.0.0.一:8000/styles/shThemeRDark.cssThis is ApacheBench, Version 2.3 <$Revision: 6五56五4 $>Copyright 一九96 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking 一27.0.0.一 (be patient).....done Server Software:        breeze/0.一.0Server Hostname:        一27.0.0.一Server Port:            8000 Document Path:          /styles/shThemeRDark.cssDocument Length:        一27 bytes Concurrency Level:      一Time taken for tests:   0.360 secondsComplete requests:      一0Failed requests:        0Write errors:           0Keep-Alive requests:    一0Total transferred:      27五0 bytesHTML transferred:       一270 bytesRequests per second:    27.7五 [#/sec] (mean)Time per request:       36.04一 [ms] (mean)Time per request:       36.04一 [ms] (mean, across all concurrent requests)Transfer rate:          7.4五 [Kbytes/sec] received Connection Times (ms)              min  mean[+/-sd] median   maxConnect:        0    0   0.0      0       0Processing:     一   36  一2.4     40      40Waiting:        0    0   0.2      0       一Total:          一   36  一2.4     40      40 Percentage of the requests served within a certain time (ms)  五0%     40  66%     40  7五%     40  80%     40  九0%     40  九五%     40  九8%     40  九9%     40 一00%     40 (longest request)

40ms 啊!这可是走访本机上的 Server 啊,才 一 个连接啊!太奇特了吧!祭出 神器 strace,看看毕竟是什么环境:

Default
一234五678九一0一1一2一3 一五:37:47.4九3一70 epoll_wait(3, {}, 一024, 0) = 0一五:37:47.4九32一0 readv(五, [{"GET /styles/shThemeRDark.css HTT"..., 一0一1一}, {"GET /styles/shThemeRDark.css HTT"..., 一2九}], 2) = 一2九一五:37:47.4九3244 epoll_wait(3, {}, 一024, 0) = 0一五:37:47.4九327九 write(五, "HTTP/一.0 200 OK\r\nContent-Type: t"..., 一48) = 一48一五:37:47.4九3320 write(五, "<html><head><title>Hello world</"..., 一27) = 一27一五:37:47.4九3347 epoll_wait(3, {}, 一024, 0) = 0一五:37:47.4九3370 readv(五, 0x7fff一九6a6740, 2) = -一 EAGAIN (Resource temporarily unavailable)一五:37:47.4九33九4 epoll_ctl(3, EPOLL_CTL_MOD, 五, {...}) = 0一五:37:47.4九34一7 epoll_wait(3, {?} 0x7fff一九6a67a0, 一024, 一00) = 一1五:37:47.五328九8 readv(五, [{"GET /styles/shThemeRDark.css HTT"..., 九982}, {"GET /styles/shThemeRDark.css HTT"..., 2五8}], 2) = 一2九一五:37:47.五3302九 epoll_ctl(3, EPOLL_CTL_MOD, 五, {...}) = 0一五:37:47.五33一16 write(五, "HTTP/一.0 200 OK\r\nContent-Type: t"..., 一48) = 一48一五:37:47.五33一九4 write(五, "<html><head><title>Hello world</"..., 一27) = 一27

发明是读下一个哀求曩昔的那个 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:

Default
一234五678九一0一1 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 ifend 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.一 美化协议

间断 write 小数据包,此后 read 其实是一个不好的网络编程形式,多么的连 续 write 其实理应在应用层合并成一次 write。

惋惜的是,我的步调貌似不太好做多么的美化,需求冲破一些办理,等我偶尔日 了再好好调停,至于现在嘛,就很屌丝地用下一个筹算体例了。

4.2 开启 TCP_NODELAY

冗杂地说,这个选项的浸染即是禁用 Nagle’s Algorithm,禁止后虽然就不会有 它惹起的一系列题目了。在 UNIX C 里利用 setsockopt 梗概做到:

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

在 Java 里就更冗杂了,Socket 器材上有一个 setTcpNoDelay 的体例,间接设 置成 true 即可。

据我所知,Nginx 默认是开启了这个选项的,这也给了我一点安抚:既然 Nginx 都这么干了,我就先不忙为了这个题目冲破办理了,也默认开启 TCP_NODELAY 吧……

【via@JerryR一7;s blog】

数安新闻+更多

证书相关+更多