九色国产,午夜在线视频,新黄色网址,九九色综合,天天做夜夜做久久做狠狠,天天躁夜夜躁狠狠躁2021a,久久不卡一区二区三区

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
40毫秒延遲與 TCP_NODELAY(轉(zhuǎn))

最近的業(yè)余時間幾乎全部獻給 breeze 這個多年前挖 下的大坑—— 一個異步 HTTP Server。努力沒有白費,項目已經(jīng)逐漸成型了, 基本的框架已經(jīng)有了,一個靜態(tài) 文件模塊也已經(jīng)實現(xiàn)了。

寫 HTTP Server,不可免俗地一定要用 ab 跑一下性能,結(jié)果一跑不打緊,出現(xiàn)了一個困擾了我好幾天的問題:神秘的 40ms 延遲。

1 現(xiàn)象

現(xiàn)象是這樣的,首先看我用 ab 不加 -k 選項的結(jié)果:

[~/dev/personal/breeze]$ /usr/sbin/ab  -c 1 -n 10 http://127.0.0.1:8000/styles/shThemeRDark.cssThis 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.0Server Hostname:        127.0.0.1Server Port:            8000Document Path:          /styles/shThemeRDark.cssDocument Length:        127 bytesConcurrency Level:      1Time taken for tests:   0.001 secondsComplete requests:      10Failed requests:        0Write errors:           0Total transferred:      2700 bytesHTML transferred:       1270 bytesRequests 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   maxConnect:        0    0   0.0      0       0Processing:     0    0   0.0      0       0Waiting:        0    0   0.0      0       0Total:          0    0   0.1      0       0Percentage of the requests served within a certain time (ms)  50%      0  66%      0  75%      0  80%      0  90%      0  95%      0  98%      0  99%      0 100%      0 (longest request)

很好,不超過 1ms 的響應(yīng)時間。但一旦我加上了 -k 選項啟用 HTTP Keep-Alive,結(jié)果就變成了這樣:

[~/dev/personal/breeze]$ /usr/sbin/ab -k  -c 1 -n 10 http://127.0.0.1:8000/styles/shThemeRDark.cssThis 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.0Server Hostname:        127.0.0.1Server Port:            8000Document Path:          /styles/shThemeRDark.cssDocument Length:        127 bytesConcurrency Level:      1Time taken for tests:   0.360 secondsComplete requests:      10Failed requests:        0Write errors:           0Keep-Alive requests:    10Total transferred:      2750 bytesHTML transferred:       1270 bytesRequests 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   maxConnect:        0    0   0.0      0       0Processing:     1   36  12.4     40      40Waiting:        0    0   0.2      0       1Total:          1   36  12.4     40      40Percentage of the requests served within a certain time (ms)  50%     40  66%     40  75%     40  80%     40  90%     40  95%     40  98%     40  99%     40 100%     40 (longest request)

40ms 啊!這可是訪問本機上的 Server 啊,才 1 個連接啊!太奇怪了吧!祭出 神器 strace,看看到底是什么情況:

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

發(fā)現(xiàn)是讀下一個請求之前的那個 epoll_wait 花了 40ms 才返回。這意味著要 么是 client 等了 40ms 才給我發(fā)請求,要么是我上面 write 寫入的數(shù)據(jù)過 了 40ms 才到達 client。前者的可能性幾乎沒有,ab 作為一個壓力測試工具, 是不可能這樣做的,那么問題只有可能是之前寫入的 response 過了 40ms 才到 達 client。

2 背后的原因

為什么延遲不高不低正好 40ms 呢?果斷 Google 一下找到了答案。原來這是 TCP 協(xié)議中的 Nagle‘s Algorithm 和 TCP Delayed Acknoledgement 共同起作 用所造成的結(jié)果。

Nagle’s Algorithm 是為了提高帶寬利用率設(shè)計的算法,其做法是合并小的TCP 包為一個,避免了過多的小報文的 TCP 頭所浪費的帶寬。如果開啟了這個算法 (默認),則協(xié)議棧會累積數(shù)據(jù)直到以下兩個條件之一滿足的時候才真正發(fā)送出 去:

  1. 積累的數(shù)據(jù)量到達最大的 TCP Segment Size
  2. 收到了一個 Ack

TCP Delayed Acknoledgement 也是為了類似的目的被設(shè)計出來的,它的作用就 是延遲 Ack 包的發(fā)送,使得協(xié)議棧有機會合并多個 Ack,提高網(wǎng)絡(luò)性能。

如果一個 TCP 連接的一端啟用了 Nagle‘s Algorithm,而另一端啟用了 TCP Delayed Ack,而發(fā)送的數(shù)據(jù)包又比較小,則可能會出現(xiàn)這樣的情況:發(fā)送端在等 待接收端對上一個packet 的 Ack 才發(fā)送當前的 packet,而接收端則正好延遲了 此 Ack 的發(fā)送,那么這個正要被發(fā)送的 packet 就會同樣被延遲。當然 Delayed Ack 是有個超時機制的,而默認的超時正好就是 40ms。

現(xiàn)代的 TCP/IP 協(xié)議棧實現(xiàn),默認幾乎都啟用了這兩個功能,你可能會想,按我 上面的說法,當協(xié)議報文很小的時候,豈不每次都會觸發(fā)這個延遲問題?事實不 是那樣的。僅當協(xié)議的交互是發(fā)送端連續(xù)發(fā)送兩個 packet,然后立刻 read 的 時候才會出現(xiàn)問題。

3 為什么只有 Write-Write-Read 時才會出問題

維基百科上的有一段偽代碼來介紹 Nagle’s Algorithm:

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

可以看到,當待發(fā)送的數(shù)據(jù)比 MSS 小的時候(外層的 else 分支),還要再判斷 時候還有未確認的數(shù)據(jù)。只有當管道里還有未確認數(shù)據(jù)的時候才會進入緩沖區(qū), 等待 Ack。

所以發(fā)送端發(fā)送的第一個 write 是不會被緩沖起來,而是立刻發(fā)送的(進入內(nèi)層 的else 分支),這時接收端收到對應(yīng)的數(shù)據(jù),但它還期待更多數(shù)據(jù)才進行處理, 所以不會往回發(fā)送數(shù)據(jù),因此也沒機會把 Ack 給帶回去,根據(jù)Delayed Ack 機制, 這個 Ack 會被 Hold 住。這時發(fā)送端發(fā)送第二個包,而隊列里還有未確認的數(shù)據(jù) 包,所以進入了內(nèi)層 if 的 then 分支,這個 packet 會被緩沖起來。此時,發(fā) 送端在等待接收端的 Ack;接收端則在 Delay 這個 Ack,所以都在等待,直到接 收端 Deplayed Ack 超時(40ms),此 Ack 被發(fā)送回去,發(fā)送端緩沖的這個 packet 才會被真正送到接收端,從而繼續(xù)下去。

再看我上面的 strace 記錄也能發(fā)現(xiàn)端倪,因為設(shè)計的一些不足,我沒能做到把 短小的 HTTP Body 連同 HTTP Headers 一起發(fā)送出去,而是分開成兩次調(diào)用實 現(xiàn)的,之后進入 epoll_wait 等待下一個 Request 被發(fā)送過來(相當于阻塞模 型里直接 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 模式,此時接收端應(yīng)該已經(jīng)得到所有需要的數(shù)據(jù)以進行 下一步處理。接收端此時處理完后發(fā)送結(jié)果,同時也就可以把上一個packet 的 Ack 可以和數(shù)據(jù)一起發(fā)送回去,不需要 delay,從而不會導(dǎo)致任何問題。

我做了一個簡單的試驗,注釋掉了 HTTP Body 的發(fā)送,僅僅發(fā)送 Headers, Content-Length 指定為 0。這樣就不會有第二個 write,變成了 write-read-write-read 模式。此時再用 ab 測試,果然沒有 40ms 的延遲了。

說完了問題,該說解決方案了。

4 解決方案

4.1 優(yōu)化協(xié)議

連續(xù) write 小數(shù)據(jù)包,然后 read 其實是一個不好的網(wǎng)絡(luò)編程模式,這樣的連 續(xù) write 其實應(yīng)該在應(yīng)用層合并成一次 write。

可惜的是,我的程序貌似不太好做這樣的優(yōu)化,需要打破一些設(shè)計,等我有時間 了再好好調(diào)整,至于現(xiàn)在嘛,就很屌絲地用下一個解決方法了。

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 的方法,直接設(shè) 置成 true 即可。

據(jù)我所知,Nginx 默認是開啟了這個選項的,這也給了我一點安慰:既然 Nginx 都這么干了,我就先不忙為了這個問題打破設(shè)計了,也默認開啟 TCP_NODELAY 吧……


本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊舉報。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
人人都該懂點兒TCP
Linux下TCP延遲確認(Delayed Ack)機制導(dǎo)致的時延問題分析
TCP的Nagle算法
PHP程序員內(nèi)功心法
HTTP請求的TCP瓶頸分析 // 灰主流創(chuàng)業(yè)者
tcpip詳解筆記(16) TCP的交互數(shù)據(jù)流
更多類似文章 >>
生活服務(wù)
熱點新聞
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點擊這里聯(lián)系客服!

聯(lián)系客服