前幾天,產品小哥哥給我反饋了一個問題:

我們某個線上系統首頁,時不時打開白屏,但是刷新一下又好了。

事出反常必有妖,這么詭異的問題,當然得排查一波啦!

隨后我打開訪問了一下,還真是,刷新個四五次差不多就會出現一次白屏。

計算機不會說謊,一定是哪里不對勁!

排查過程

我們這系統是使用nginx+多臺業務服務器部署的架構,nginx充當代理轉發,也起到負載均衡的作用。

我使用內部的地址單獨訪問了背后的每一臺業務服務器,刷新多次,都沒有出現這個問題。

而一旦使用nginx代理后的域名訪問,就會出現。

直覺和經驗告訴我:問題肯定出在轉發這里。

思來想去,還是從前端開始入手來排查。

隨后我開了兩個瀏覽器窗口,一個正常打開,一個白屏。

祭出F12大法,準備比較一下兩種情況下的差異。

先來看看瀏覽器的控制臺窗口,果然有所發現:

點擊過去查看詳情,發現報錯的正是要加載的首頁的HTML網頁內容:

網頁內容被壓縮了,使用瀏覽器的格式化工具,將其格式化成方便閱讀的模式,錯誤位置進一步鎖定在一個JS腳本這里:

我單獨請求了一下這個JS文件,并沒有什么異常。

再拿這個HTML網頁內容和那個能正常打開的窗口里的HTML內容比較一下,發現并沒有什么兩樣。

真是怪了!

正在迷惑之際,控制臺窗口的網絡連接信息發現了線索:

兩個瀏覽器窗口請求同一個JS文件,正常那個是200,白屏那個是302!

為什么會有302的出現?

我將域名替換成內部業務服務器的地址,繞開nginx,單獨向每一臺服務器請求這個JS文件。

果然!

有一臺給我返回了302!

而且不管怎么刷新,它總是返回302,其他幾臺都能正常返回。

接著,我登錄了這臺服務器,檢查對應路徑下的JS文件,確實有一個文件,但名字卻不同:

注意文件名中間那一串十六進制數字,跟前面請求的東西不是同一個。

另外幾臺機器我也檢查了,沒有問題,名字跟請求的一致。

咱也不是專業的前端,只知道這個名字是VUE打包后生成的,每一次打包都會不同。

看來這一臺出問題的服務器上使用的前端資源包版本跟其他幾臺不一樣。

只要將這臺服務器的前端資源更新,問題就可解決。

為什么白屏?

接下來就是來解釋一個問題:為什么單獨請求每一臺服務器能正常打開頁面,而經過nginx轉發后會出現白屏的現象?

要回答這個問題,先得來理解一下瀏覽器渲染一個頁面的基本過程。

當輸入一個頁面地址后,瀏覽器首先取回這個地址背后的HTML網頁。

瀏覽器收到后,在解析HTML網頁的時候,會發現網頁中又引入了JS、CSS、圖片等這些資源文件,于是又去請求它們。

注意,這里就有一個問題:

請求HTML網頁的動作和請求這些資源的動作,是放在同一個TCP連接中進行,還是分開單獨建立TCP連接進行?

這個問題也正是HTTP協議的1.1版本對1.0版本的一個重要升級。

在HTTP 1.0版本中,默認是每個資源單獨建立TCP連接去請求。

在HTTP 1.1版本中,引入了長連接機制——keep-alive,可以在同一條TCP連接中請求多個資源。

那這跟白屏又有什么關系呢?

nginx的轉發是基于連接的,同一個連接中的多個請求會轉發給同一個服務器。

這樣,HTML和它里面嵌入的那些資源,都是走的同一個連接,發到了同一臺服務器,HTML中引入的JS文件名字和這臺服務器上存放的JS文件名字是匹配的。

反之,如果HTML請求和那些資源的請求,走的是不同的連接,就可能會被nginx轉發到不同的服務器,就可能會出現HTML里面引入的JS文件名,和被轉發到的服務器上存放的JS文件資源不匹配,張冠李戴了!

而當我繞過nginx,直接使用內部域名來請求時,HTML和資源請求不管是不是走的同一個連接,都是那一臺服務器負責處理,雖然這臺服務器跟別的服務器前端包的版本不同,但其HTML和JS是匹配的,所以不會出現張冠李戴的現象,也就不會白屏。

那基于這個設定,瀏覽器到nginx的連接肯定就不是長連接,而是短連接了!

我抓包驗證了一下:

好家伙,看看這是多少條連接。

再點進去看一下:

好家伙,nginx居然用的HTTP 1.0!

真相自此大白!

標簽: 負載均衡