2021-02-27 分類: 網(wǎng)站建設(shè)
1. 背景
最近項(xiàng)目測試遇到個(gè)奇怪的現(xiàn)象,在測試環(huán)境通過 Apache HttpClient 調(diào)用后端的 HTTP 服務(wù),平均耗時(shí)居然接近 39.2ms。可能你乍一看覺得這不是很正常嗎,有什么好奇怪的?其實(shí)不然,我再來說下一些基本信息,該后端的 HTTP 服務(wù)并沒有什么業(yè)務(wù)邏輯,只是將一段字符串轉(zhuǎn)成大寫然后返回,字符串長度也僅只有 100 字符,另外網(wǎng)絡(luò) ping 延時(shí)只有 1.9ms左右。因此,理論上該調(diào)用耗時(shí)應(yīng)該在 2-3ms 左右,但為什么平均耗時(shí) 39.2ms 呢?
由于工作原因,調(diào)用耗時(shí)的問題,對我來說,已經(jīng)見怪不怪了,經(jīng)常會(huì)幫業(yè)務(wù)解決內(nèi)部 RPC 框架調(diào)用超時(shí)的相關(guān)問題,但是 HTTP 調(diào)用耗時(shí)***次遇到。不過,排查問題的套路是一樣的。主要方法論無外乎由外而內(nèi)、至上而下等排查方法。我們先來看看外圍的一些指標(biāo),看能否發(fā)現(xiàn)蛛絲馬跡。
2. 外圍指標(biāo)
2.1 系統(tǒng)指標(biāo)
主要看外圍的一些系統(tǒng)指標(biāo)(注意:調(diào)用與被調(diào)用的機(jī)器都要看)。例如負(fù)載、CPU。只需一個(gè) top 命令就能一覽無余。
因此,確認(rèn)了下 CPU 和負(fù)載都很空閑。由于當(dāng)時(shí)沒有截圖,這里就不放圖了。
2.2 進(jìn)程指標(biāo)
Java 程序進(jìn)程指標(biāo)主要看 GC、線程堆棧情況(注意:調(diào)用與被調(diào)用的機(jī)器都要看)。
Young GC 都非常少,而且耗時(shí)也在 10ms 以內(nèi),因此沒有長時(shí)間的 STW。
因?yàn)槠骄{(diào)用時(shí)間 39.2ms,比較大,如果耗時(shí)是代碼導(dǎo)致,線程堆棧應(yīng)該能發(fā)現(xiàn)點(diǎn)啥??戳酥笠粺o所獲,服務(wù)的相關(guān)線程堆棧主要表現(xiàn)是線程池的線程在等任務(wù),這就意味著線程并不忙。
是不是感覺黔驢技窮了,接下來該怎么辦呢?
3. 本地復(fù)現(xiàn)
如果本地(本地是 MAC 系統(tǒng))能復(fù)現(xiàn),對排查問題也是極好的。
因此在本地使用 Apache HttpClient 寫了個(gè)簡單 Test 程序,直接調(diào)用后端的 HTTP 服務(wù),發(fā)現(xiàn)平均耗時(shí)在 55ms 左右。咦,怎么跟測試環(huán)境 39.2ms 的結(jié)果有點(diǎn)區(qū)別。主要是本地與測試環(huán)境的后端的 HTTP 服務(wù)機(jī)器跨地區(qū)了,ping 時(shí)延在 26ms 左右,所以延時(shí)增大了。不過本地確實(shí)也是存在問題的,因?yàn)閜ing 時(shí)延是 26ms,后端 HTTP 服務(wù)邏輯簡單,幾乎不耗時(shí),因此本地調(diào)用平均耗時(shí)應(yīng)該在 26ms 左右,為什么是 55ms?
是不是越來越迷惑,一頭霧水,不知如何下手?
期間懷疑過 Apache HttpClient 是不是有什么地方使用的不對,因此使用 JDK 自帶的 HttpURLConnection 寫了簡單的程序,做了測試,結(jié)果一樣。
4. 診斷
4.1 定位
其實(shí)從外圍的系統(tǒng)指標(biāo)、進(jìn)程指標(biāo),以及本地復(fù)現(xiàn)來看,大致能夠斷定不是程序上的原因。那 TCP 協(xié)議層面呢?
有網(wǎng)絡(luò)編程經(jīng)驗(yàn)的同學(xué)一定知道 TCP 什么參數(shù)會(huì)引起這個(gè)現(xiàn)象。對,你猜的沒錯(cuò),就是 TCP_NODELAY。
那調(diào)用方和被調(diào)用方哪邊的程序沒有設(shè)置呢?
調(diào)用方使用的是 Apache HttpClient ,tcpNoDelay 默認(rèn)設(shè)置的就是 true。我們再來看看被調(diào)用方,也就是我們的后端 HTTP 服務(wù),這個(gè) HTTP 服務(wù)用的是 JDK自帶的 HttpServer
- HttpServer server = HttpServer.create(new InetSocketAddress(config.getPort()), BACKLOGS);
居然沒看到直接設(shè)置 tcpNoDelay 接口,翻了下源碼。哦,原來在這里,在 ServerConfig 的類中有這段靜態(tài)塊,用來獲取啟動(dòng)參數(shù),默認(rèn) ServerConfig.noDelay 為 false。
- static {
- AccessController.doPrivileged(new PrivilegedAction() {
- public Void run() {
- ServerConfig.idleInterval = Long.getLong("sun.net.httpserver.idleInterval", 30L) * 1000L;
- ServerConfig.clockTick = Integer.getInteger("sun.net.httpserver.clockTick", 10000);
- ServerConfig.maxIdleConnections = Integer.getInteger("sun.net.httpserver.maxIdleConnections", 200);
- ServerConfig.drainAmount = Long.getLong("sun.net.httpserver.drainAmount", 65536L);
- ServerConfig.maxReqHeaders = Integer.getInteger("sun.net.httpserver.maxReqHeaders", 200);
- ServerConfig.maxReqTime = Long.getLong("sun.net.httpserver.maxReqTime", -1L);
- ServerConfig.maxRspTime = Long.getLong("sun.net.httpserver.maxRspTime", -1L);
- ServerConfig.timerMillis = Long.getLong("sun.net.httpserver.timerMillis", 1000L);
- ServerConfig.debug = Boolean.getBoolean("sun.net.httpserver.debug");
- ServerConfig.noDelay = Boolean.getBoolean("sun.net.httpserver.nodelay");
- return null;
- }
- });
- }
4.2 驗(yàn)證
在后端 HTTP 服務(wù),加上啟動(dòng)"-Dsun.net.httpserver.nodelay=true"參數(shù),再試試。效果很明顯,平均耗時(shí)從39.2ms 降到 2.8ms。
問題是解決了,但是到這里如果你就此止步,那就太便宜了這個(gè)案例了,簡直暴殄天物。因?yàn)檫€有一堆疑惑等著你呢?
來,我們接著乘熱打鐵。
5. 解惑
5.1 TCP_NODELAY 何許人也?
在 Socket 編程中,TCP_NODELAY 選項(xiàng)是用來控制是否開啟 Nagle 算法。在 Java 中,為 ture 表示關(guān)閉 Nagle 算法,為 false 表示打開 Nagle 算法。你一定會(huì)問 Nagle 算法是什么?
5.2 Nagle 算法是什么鬼?
Nagle 算法是一種通過減少通過網(wǎng)絡(luò)發(fā)送的數(shù)據(jù)包數(shù)量來提高 TCP/IP 網(wǎng)絡(luò)效率的方法。它使用發(fā)明人 John Nagle 的名字來命名的,John Nagle 在 1984 年***用這個(gè)算法來嘗試解決福特汽車公司的網(wǎng)絡(luò)擁塞問題。
試想如果應(yīng)用程序每次產(chǎn)生 1 個(gè)字節(jié)的數(shù)據(jù),然后這 1 個(gè)字節(jié)數(shù)據(jù)又以網(wǎng)絡(luò)數(shù)據(jù)包的形式發(fā)送到遠(yuǎn)端服務(wù)器,那么就很容易導(dǎo)致網(wǎng)絡(luò)由于太多的數(shù)據(jù)包而過載。在這種典型情況下,傳送一個(gè)只擁有1個(gè)字節(jié)有效數(shù)據(jù)的數(shù)據(jù)包,卻要花費(fèi) 40 個(gè)字節(jié)長包頭(即 IP 頭部 20 字節(jié) + TCP 頭部 20 字節(jié))的額外開銷,這種有效載荷(payload)的利用率是極其低下。
Nagle 算法的內(nèi)容比較簡單,以下是偽代碼:
- 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
具體的做法就是:
5.3 Delayed ACK 又是什么玩意?
大家都知道 TCP 協(xié)議為了保證傳輸?shù)目煽啃?,?guī)定在接受到數(shù)據(jù)包時(shí)需要向?qū)Ψ桨l(fā)送一個(gè)確認(rèn)。只是單純的發(fā)送一個(gè)確認(rèn),代價(jià)會(huì)比較高(IP 頭部 20 字節(jié) + TCP 頭部 20 字節(jié))。TCP Delayed ACK(延遲確認(rèn))就是為了努力改善網(wǎng)絡(luò)性能,來解決這個(gè)問題的,它將幾個(gè) ACK 響應(yīng)組合合在一起成為單個(gè)響應(yīng),或者將 ACK 響應(yīng)與響應(yīng)數(shù)據(jù)一起發(fā)送給對方,從而減少協(xié)議開銷。
具體的做法是:
5.4 Nagle 與 Delayed ACK 一起會(huì)發(fā)生什么化學(xué)反應(yīng)?
Nagle 與 Delayed ACK 都能提高網(wǎng)絡(luò)傳輸?shù)男?,但在一起?huì)好心辦壞事。例如,以下這個(gè)場景:
A 和 B 進(jìn)行數(shù)據(jù)傳輸 : A 運(yùn)行 Nagle 算法,B 運(yùn)行 Delayed ACK 算法。
如果 A 向 B 發(fā)一個(gè)數(shù)據(jù)包,B 由于 Delayed ACK 不會(huì)立即響應(yīng)。而 A 使用 Nagle 算法,A 就會(huì)一直等 B 的 ACK,ACK 不來一直不發(fā)送第二個(gè)數(shù)據(jù)包,如果這兩個(gè)數(shù)據(jù)包是應(yīng)對同一個(gè)請求,那這個(gè)請求就會(huì)被耽誤了 40ms。
5.5 抓個(gè)包玩玩吧
我們來抓個(gè)包驗(yàn)證下吧,在后端HTTP服務(wù)上執(zhí)行以下腳本,就可以輕松完成抓***程。
- sudo tcpdump -i eth0 tcp and host 10.48.159.165 -s 0 -w traffic.pcap
如下圖,這是使用 Wireshark 分析包內(nèi)容的展示,紅框內(nèi)是一個(gè)完整的 POST 請求處理過程,看 130 序號和 149 序號之間相差 40ms(0.1859 - 0.1448 = 0.0411s = 41ms),這個(gè)就是 Nagle 與 Delayed ACK 一起發(fā)送的化學(xué)反應(yīng),其中 10.48.159.165 運(yùn)行的是 Delayed ACK,10.22.29.180 運(yùn)行的是 Nagle 算法。10.22.29.180 在等 ACK,而 10.48.159.165 觸發(fā)了 Delayed ACK,這樣傻傻等了 40ms。
這也就解釋了為什么測試環(huán)境耗時(shí)是 39.2ms,因?yàn)榇蟛糠侄急?Delayed ACK 的 40ms 給耽誤了。
但是本地復(fù)現(xiàn)時(shí),為什么本地測試的平均時(shí)延是 55ms,而不是 ping 的時(shí)延 26ms?我們也來抓個(gè)包吧。
如下圖,紅框內(nèi)是一個(gè)完整的 POST 請求處理過程,看 8 序號和 9 序號之間相差 25ms 左右,再減去網(wǎng)絡(luò)延時(shí)約是ping延時(shí)的一半 13ms,因此 Delayed Ack 約 12ms 左右(由于本地是 MAC 系統(tǒng)與 Linux 有些差異)。
1. Linux 使用的是 /proc/sys/net/ipv4/tcp_delack_min 這個(gè)系統(tǒng)配置來控制 Delayed ACK 的時(shí)間,Linux 默認(rèn)是 40ms;
2. MAC 是通過 net.inet.tcp.delayed_ack 系統(tǒng)配置來控制 Delayed ACK 的。
delayed_ack=0 responds after every packet (OFF) delayed_ack=1 always employs delayed ack, 6 packets can get 1 ack delayed_ack=2 immediate ack after 2nd packet, 2 packets per ack (Compatibility Mode) delayed_ack=3 should auto detect when to employ delayed ack, 4packets per ack. (DEFAULT)設(shè)置為 0 表示禁止延遲 ACK,設(shè)置為 1 表示總是延遲 ACK,設(shè)置為 2 表示每兩個(gè)數(shù)據(jù)包回復(fù)一個(gè) ACK,設(shè)置為 3 表示系統(tǒng)自動(dòng)探測回復(fù) ACK 的時(shí)機(jī)。
5.6 為什么 TCP_NODELAY 能夠解決問題?
TCPNODELAY 關(guān)閉了 Nagle 算法,即使上個(gè)數(shù)據(jù)包的 ACK 沒有到達(dá),也會(huì)發(fā)送下個(gè)數(shù)據(jù)包,進(jìn)而打破 Delayed ACK 造成的影響。一般在網(wǎng)絡(luò)編程中,強(qiáng)烈建議開啟 TCPNODELAY,來提升響應(yīng)速度。
當(dāng)然也可以通過 Delayed ACK 相關(guān)系統(tǒng)的配置來解決問題,但由于需要修改機(jī)器配置,很不方便,因此,這種方式不太推薦。
6. 總結(jié)
本文是從一個(gè)簡單的 HTTP 調(diào)用,時(shí)延比較大而引發(fā)的一次問題排查過程。過程中,首先由外而內(nèi)的分析了相關(guān)問題,然后定位問題并驗(yàn)證解決方案。***刨根問底對 TCP 傳輸?shù)闹械?Nagle 與 Delayed ACK 做了全面的講解,更加透測的剖析了該問題案例。
作者:滌生,姓名殷琦,目前就職于某大型互聯(lián)網(wǎng)公司基礎(chǔ)架構(gòu)部,主要負(fù)責(zé)微服務(wù)框架、服務(wù)治理、Serverless 相關(guān)工作。 《高可用可伸縮微服務(wù)架構(gòu):基于Dubbo、Spring Cloud和Service Mesh》的作者之一。
當(dāng)前標(biāo)題:簡單的 HTTP 調(diào)用,為什么時(shí)延這么大?
本文網(wǎng)址:http://jinyejixie.com/news40/103240.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供營銷型網(wǎng)站建設(shè)、搜索引擎優(yōu)化、軟件開發(fā)、網(wǎng)站改版、全網(wǎng)營銷推廣、品牌網(wǎng)站設(shè)計(jì)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容