計(jì)算機(jī)不會騙人,事出反常必有妖!
前幾天,產(chǎn)品小哥哥給我反饋了一個問題:
我們某個線上系統(tǒng)首頁,時不時打開白屏,但是刷新一下又好了。
事出反常必有妖,這么詭異的問題,當(dāng)然得排查一波啦!
隨后我打開訪問了一下,還真是,刷新個四五次差不多就會出現(xiàn)一次白屏。
計(jì)算機(jī)不會說謊,一定是哪里不對勁!
排查過程
我們這系統(tǒng)是使用nginx+多臺業(yè)務(wù)服務(wù)器部署的架構(gòu),nginx充當(dāng)代理轉(zhuǎn)發(fā),也起到負(fù)載均衡的作用。
我使用內(nèi)部的地址單獨(dú)訪問了背后的每一臺業(yè)務(wù)服務(wù)器,刷新多次,都沒有出現(xiàn)這個問題。
而一旦使用nginx代理后的域名訪問,就會出現(xiàn)。
直覺和經(jīng)驗(yàn)告訴我:問題肯定出在轉(zhuǎn)發(fā)這里。
思來想去,還是從前端開始入手來排查。
隨后我開了兩個瀏覽器窗口,一個正常打開,一個白屏。
祭出F12大法,準(zhǔn)備比較一下兩種情況下的差異。
先來看看瀏覽器的控制臺窗口,果然有所發(fā)現(xiàn):
點(diǎn)擊過去查看詳情,發(fā)現(xiàn)報(bào)錯的正是要加載的首頁的HTML網(wǎng)頁內(nèi)容:
網(wǎng)頁內(nèi)容被壓縮了,使用瀏覽器的格式化工具,將其格式化成方便閱讀的模式,錯誤位置進(jìn)一步鎖定在一個JS腳本這里:
我單獨(dú)請求了一下這個JS文件,并沒有什么異常。
再拿這個HTML網(wǎng)頁內(nèi)容和那個能正常打開的窗口里的HTML內(nèi)容比較一下,發(fā)現(xiàn)并沒有什么兩樣。
真是怪了!
正在迷惑之際,控制臺窗口的網(wǎng)絡(luò)連接信息發(fā)現(xiàn)了線索:
兩個瀏覽器窗口請求同一個JS文件,正常那個是200,白屏那個是302!
為什么會有302的出現(xiàn)?
我將域名替換成內(nèi)部業(yè)務(wù)服務(wù)器的地址,繞開nginx,單獨(dú)向每一臺服務(wù)器請求這個JS文件。
果然!
有一臺給我返回了302!
而且不管怎么刷新,它總是返回302,其他幾臺都能正常返回。
接著,我登錄了這臺服務(wù)器,檢查對應(yīng)路徑下的JS文件,確實(shí)有一個文件,但名字卻不同:
注意文件名中間那一串十六進(jìn)制數(shù)字,跟前面請求的東西不是同一個。
另外幾臺機(jī)器我也檢查了,沒有問題,名字跟請求的一致。
咱也不是專業(yè)的前端,只知道這個名字是VUE打包后生成的,每一次打包都會不同。
看來這一臺出問題的服務(wù)器上使用的前端資源包版本跟其他幾臺不一樣。
只要將這臺服務(wù)器的前端資源更新,問題就可解決。
為什么白屏?
接下來就是來解釋一個問題:為什么單獨(dú)請求每一臺服務(wù)器能正常打開頁面,而經(jīng)過nginx轉(zhuǎn)發(fā)后會出現(xiàn)白屏的現(xiàn)象?
要回答這個問題,先得來理解一下瀏覽器渲染一個頁面的基本過程。
當(dāng)輸入一個頁面地址后,瀏覽器首先取回這個地址背后的HTML網(wǎng)頁。
瀏覽器收到后,在解析HTML網(wǎng)頁的時候,會發(fā)現(xiàn)網(wǎng)頁中又引入了JS、CSS、圖片等這些資源文件,于是又去請求它們。
注意,這里就有一個問題:
請求HTML網(wǎng)頁的動作和請求這些資源的動作,是放在同一個TCP連接中進(jìn)行,還是分開單獨(dú)建立TCP連接進(jìn)行?
這個問題也正是HTTP協(xié)議的1.1版本對1.0版本的一個重要升級。
在HTTP 1.0版本中,默認(rèn)是每個資源單獨(dú)建立TCP連接去請求。
在HTTP 1.1版本中,引入了長連接機(jī)制——keep-alive,可以在同一條TCP連接中請求多個資源。
那這跟白屏又有什么關(guān)系呢?
nginx的轉(zhuǎn)發(fā)是基于連接的,同一個連接中的多個請求會轉(zhuǎn)發(fā)給同一個服務(wù)器。
這樣,HTML和它里面嵌入的那些資源,都是走的同一個連接,發(fā)到了同一臺服務(wù)器,HTML中引入的JS文件名字和這臺服務(wù)器上存放的JS文件名字是匹配的。
反之,如果HTML請求和那些資源的請求,走的是不同的連接,就可能會被nginx轉(zhuǎn)發(fā)到不同的服務(wù)器,就可能會出現(xiàn)HTML里面引入的JS文件名,和被轉(zhuǎn)發(fā)到的服務(wù)器上存放的JS文件資源不匹配,張冠李戴了!
而當(dāng)我繞過nginx,直接使用內(nèi)部域名來請求時,HTML和資源請求不管是不是走的同一個連接,都是那一臺服務(wù)器負(fù)責(zé)處理,雖然這臺服務(wù)器跟別的服務(wù)器前端包的版本不同,但其HTML和JS是匹配的,所以不會出現(xiàn)張冠李戴的現(xiàn)象,也就不會白屏。
那基于這個設(shè)定,瀏覽器到nginx的連接肯定就不是長連接,而是短連接了!
我抓包驗(yàn)證了一下:
好家伙,看看這是多少條連接。
再點(diǎn)進(jìn)去看一下:
好家伙,nginx居然用的HTTP 1.0!
真相自此大白!