簡述
最近項目中出現一個問題,前端每隔1秒向同一個url發起請求,第一次請求響應時間2秒左右,此后每次請求耗時會增加大約1秒,直到超時。
定位和驗證
- 后台
增加日志觀察后台服務耗時情況,發現每次均耗時2秒左右,和前端第一次請求耗時差不多,后續也沒有明顯增長,基本可以排除后台服務的問題
- 前端
首先是在項目指定的瀏覽器chrome上發現的問題,之后分別測試了edeg/firefox/ie,只在edeg上復現了問題,而edeg又是使用的Chromium內核,因此猜測是Chrome的某種機制導致的問題。
打開chrome控制台,查看請求耗時的詳情,如下:
觀察多次請求的耗時明細,發現真正發起請求到響應的時間依然穩定在2秒左右,這和后台觀察到的情況是一樣的。
真正導致請求超時的是Connection Start:Stalled,這一項每次穩定增長1秒左右,最終導致超時。
那么這個Stalled是何方神聖呢,chrome文檔(https://developer.chrome.com/docs/devtools/network/reference/)如下:
Here's more information about each of the phases you may see in the Timing tab:
-
- Queueing. The browser queues requests when:
- There are higher priority requests. 有更高優先級的請求
- There are already six TCP connections open for this origin, which is the limit. Applies to HTTP/1.0 and HTTP/1.1 only. 針對每個源,最多打開6個TCP連接
- The browser is briefly allocating space in the disk cache 瀏覽器正在准備緩存
- Stalled. The request could be stalled for any of the reasons described in Queueing.
- Queueing. The browser queues requests when:
Stalled:在滿足Queueing的任意一種條件時,請求將會停滯(阻塞)。
更高優先級的請求此時並不存在,排除;TCP連接數量限制此時也未達上限,而且同網站的其他請求並未受影響,排除(對於TCP連接和數量限制后續可以再研究一下);
同時在StackOverFlow上我找到一個類似的問題:https://stackoverflow.com/questions/27513994/chrome-stalls-when-making-multiple-requests-to-same-resource,高贊回答如下:
This behavior is due to Chrome locking the cache and waiting to see the result of one request before requesting the same resource again. The answer is to find a way to make the requests unique.
I added a random number to the query string, and everything is working now.
完美契合文檔中的緩存阻塞,馬上驗證
驗證1:在請求url上添加隨機值
將url由http://localhost:8080/master/timed修改為http://localhost:8080/master/timed?HxkNkz4Wwe
結果:問題解決,所有請求並發進行,不再阻塞!
那么能不能通過后端的響應來解決問題呢?於是又做了一些驗證
驗證2:修改響應頭,試圖控制瀏覽器的緩存行為
分別修改Cache-Control響應頭的值為no-store/no-cache/no-store,no-cache/max-age=3, must-revalidate,觀察
結果:前面3種毫無反應;對於第4種,結果如下:
沒有緩存時,請求從后台獲取數據,然后緩存到本地;有緩存時,請求直接從磁盤緩存獲取數據(有時候還會獲取到空值),對於接口類的請求來說這有可能獲取到過期的數據,顯然是不可接受的,事實上,一般之后資源類的數據(js/css/圖片等)才會通過緩存獲取。
另外,針對文檔中的第二點,TCP連接數量限制也做了一次驗證:
驗證3:最大TCP連接數
將前端請求頻率縮小為10ms一次並且帶上隨機值,並發數量超過6個以后的確會發生阻塞現象:
如圖,即使url不同也只能同時發出6個請求,前面的請求完成之后后面的才能進行。
結論
在拿到響應之前,chrome會將資源(以url表示)相關的緩存鎖住,后續所有相同的url請求都必須在隊列中等待,直到前面的請求及緩存處理完之后才能依次進行。
解決這個問題有兩種方法:
1. 每次請求時在鏈接上加個隨機值
2. 對於完全相同的請求不並發進行,而是等上一個完成之后才進行下一個
Tips
這次解決這個問題花的時間稍微有點長,原因是一開始的方向有誤,直接去翻后台代碼和腳本浪費了不少時間。
正確的做法是首先確定問題發生在哪里:前端還是后端,如果是后端的話再看是服務器還是服務本身(老二分法了),確定是服務本身之后再去看細節,不花無謂的時間。