大多數(shù)人應(yīng)該聽過一道經(jīng)典的面試題:請詳細(xì)地說出從瀏覽器地址欄輸入 url 到最終呈現(xiàn)出結(jié)果的過程,越詳細(xì)越好,為什么面試官這么喜歡問這道題呢,因為這個題涉及的面非常廣,知識點非常多,如果你能完全吃透,非常有助于排查一些疑難雜癥。
今天我要分享的這個 case 就是個典型,廢話不多說,進(jìn)入正題。
前端同學(xué)發(fā)現(xiàn)新開發(fā)的項目接口有 1/3 概率出現(xiàn) RTT(請求往返時間)大于 3 s 的情況,以登錄接口為例,Chrome 請求所花時間如下:

正常的 RTT 在幾十 ms 左右,所以 3s 這個時延肯定不正常,于是著手排查,由于每個接口都可能超過 3s,所以下文皆以登錄接口分析為例,因為登錄接口邏輯相對比較簡單。
- 排除瀏覽器本身的問題
估計大家看到這種問題馬上就斷定是 server 的問題,立馬開始著手排查 server 的問題,不急,我們要先把瀏覽器本身可能導(dǎo)致請求緩慢的問題給排除了,瀏覽器本身可能因為「請求最大并發(fā)數(shù)量限制」,「更高優(yōu)先級的請求插隊,低優(yōu)先級的任務(wù)被延后」等原因?qū)е抡埱缶徛?/p>
為了排除瀏覽器本身造成的網(wǎng)絡(luò)請求過慢,我們最好找一個其它的瀏覽器比如 Safari 或者終端 curl 請求來再重試下這個請求,看下請求是否依然緩慢,這里我兩個方法都試了,用 Safari 也重現(xiàn)了 RTT 大于 3s 的情況。
用 curl 在終端請求也發(fā)現(xiàn)了 RTT 大于 3s 的情況,如何使用 curl 請求呢,這里提醒一下,大家千萬不要傻傻地去構(gòu)建一個 curl 請求,瀏覽器的網(wǎng)絡(luò)請求本身可以導(dǎo)出成 curl 的形式的,在「network」中點擊網(wǎng)絡(luò)請求,選中「Copy as cURL」,就可獲取此請求的 curl 表示方式

獲得 curl 請求后,對此請求稍加改造,加上如下 -o,-s, -w 選項
$ curl -o /dev/null -s -w \
"time_connect: %{time_connect} \n \
time_starttransfer: %{time_starttransfer} \n \
time_nslookup:%{time_namelookup} \n \
time_total: %{time_total} \n" \
https://www.example.com
這里對幾個參數(shù)簡要作下說明:
-o /dev/null 屏蔽原有輸出信息,-s 靜默模式,不輸出任何東西,-w %{http_code} 控制額外輸出。
請求后會出現(xiàn)類似以下的結(jié)果
time_connect: 0.045897
time_starttransfer: 3.064762
time_nslookup:0.004328
time_total: 3.064777
前面幾個參數(shù)就不多做說明了,一般我們只要關(guān)心最后一個 time_total 參數(shù)即可,表示請求花費的全部時間。
從流量的流轉(zhuǎn)路徑著手了,客戶端發(fā)出請求要經(jīng)過哪些流程才能到達(dá)后端服務(wù)?

可以看到請求需要經(jīng)過反向代理層,接入層后才能到達(dá)我們的站點層(即我們的 Spring MVC 服務(wù)),也就是說從「反向代理層到接入層」及「接入層到站點層」都可能導(dǎo)致請求緩慢,于是我把我用 arthas trace 執(zhí)行的結(jié)果(MVC 服務(wù)執(zhí)行時間 80ms 左右)與前端請求有 1/3 的概率超過 3s 的結(jié)論告訴了運維,讓他們排查一下從反向代理層到站點層這中間是否有啥問題,不一會兒果然查出了問題。
結(jié)論是這樣的:本來 MVC 服務(wù)的機(jī)器有三臺,后來縮容了一臺,變成了兩臺,但接入層 kongfu 依然持有這臺被縮容的機(jī)器 ip,沒有將其踢掉,所以前端流量打進(jìn)來后,由于接入層的負(fù)載均衡策略,請求是有 1/3 的概率打到這臺下線機(jī)器的 ip 上的,由于這個 ip 對應(yīng)的機(jī)器無法響應(yīng)這個請求,等到超時后,kongfu 會重試把這個請求打到另外正常的兩臺機(jī)器中的任意一臺,也就是說請求 3s 中的大部分時間花在了等待那臺不正常的 ip 機(jī)器響應(yīng)上了。

有人可能會問,機(jī)器被踢掉了,接入層 kongfu 應(yīng)該是能檢測其下線的吧,怎么還會給這臺下線的機(jī)器發(fā)請求呢?
是的,kongfu 會通過端口檢測來檢測機(jī)器是否存活的,但問題是,這臺被縮容的機(jī)器雖然被回收了,但它的 ip 也是可以重新被分配給其他機(jī)器的,這種情況下 kongfu 通過端口檢測就會認(rèn)為它持有的 ip 對應(yīng)的機(jī)器是存活的,而這臺被分配此 ip 的機(jī)器又剛好不是 Spring MVC 服務(wù),那正常 MVC 請求打給它的話,它就無法處理了,只能等到請求超時再由 kongfu 重試轉(zhuǎn)發(fā)給正常的機(jī)器。
有人可能會好奇,運維是怎么查出來的呢,通過 「curl -I www.example」的形式可以輸出開頭信息,然后加上 -b 選項可以帶上 cookie,我們的接入層如果發(fā)現(xiàn)請求里帶有某些特殊的 cookie 會返回一個名為「X-KF-Via」的頭字段,如下:
X-KF-Via: agw(bip=10.65.x.1:8001,10.65.x.2:8001;b=mvc_service)
這個頭字段表示,請求 mvc_service 服務(wù)總共請求了兩臺機(jī)器,第一臺 10.65.x.1 未成功后,再接著重試 10.65.x.2:8001,所以由此可以排查出 10.65.x.1 這臺機(jī)器有問題,所以你看熟悉系統(tǒng)架構(gòu)有多重要,如果我早知道有這么一個選項,就可以一步到位排查出此問題了.
知道了問題所在,處理方案就很簡單了,直接把這臺有問題的機(jī)器從 kongfu 摘掉就行了!
排查的思路其實相對比較清晰,但一定要對請求的整個流轉(zhuǎn)流程有一個比較清醒的認(rèn)識,這樣才能快速判斷出問題所在,現(xiàn)在再回頭看下開頭的那道經(jīng)典面試題「請詳細(xì)地說出從瀏覽器地址欄輸入 url 到最終呈現(xiàn)出結(jié)果的過程,越詳細(xì)越好」,相信你會頗有感觸,這道面試題如果你對請求流轉(zhuǎn)的每個點都吃透得話,將極大地提升你排查解決問題的能力,舉個例子,之前就有人反饋這樣的一個問題:
在做 Server 壓力測試時發(fā)現(xiàn),客戶端給服務(wù)器不斷發(fā)請求,并接受服務(wù)器端的響應(yīng)。
發(fā)現(xiàn)接收服務(wù)器響應(yīng)的過程中,會出現(xiàn) recv 服務(wù)器端響應(yīng),阻塞 40ms 的情況,但是查看 server 端日志,Server 都在 2ms 內(nèi)將請求處理完成,并給客戶端響應(yīng)
如果你了解 TCP,就知道它是由于 TCP 的延遲確認(rèn)機(jī)制和 Nagle 算法及擁塞控制導(dǎo)致的,自然而然就會朝著這個方向 去解決了,比如打開 TCP_NODELAY 選項等。
參考
RT 過長,排查思路
https://bestxf.blog.csdn.net/article/details/118193092
記錄線上RT規(guī)律性增長問題排查
http://www.itdecent.cn/p/0c0e5c438116
瀏覽器、 postman操作和curl命令互轉(zhuǎn)
http://www.itdecent.cn/p/a6310e0fd290
用curl查看http請求各階段響應(yīng)時間
http://www.itdecent.cn/p/1f6e21e75841