API 請求慢?這次鍋真不在后端
問題
我們在開發(fā)過程中,發(fā)現(xiàn)后端 API 請求特別慢,于是跟后端抱怨。
“怎么 API 這么慢啊,請求一個(gè)接口要十幾秒”。
而且這種情況是偶現(xiàn)的,前端開發(fā)同學(xué)表示有時(shí)候會(huì)出現(xiàn),非必現(xiàn)。
但是后端同學(xué)通過一頓操作后發(fā)現(xiàn),接口沒有問題,他們是通過 postman 工具以及 test 環(huán)境嘗試,都發(fā)現(xiàn)接口請求速度是沒有問題的。
“那感覺是前端問題”?
我們來梳理一下問題,如下:
- 后端 API 請求特別慢,而且是偶現(xiàn)的。
- 在 test 環(huán)境沒有復(fù)現(xiàn)。
- postman 工具請求沒有復(fù)現(xiàn)。
問題解決過程
時(shí)間都去哪了?
第一個(gè)問題,API 耗費(fèi)的時(shí)間都用來做什么了?
我們打開 Chrome 調(diào)試工具。在 network 中可以看到每個(gè)接口的耗時(shí)。
hover 到你的耗時(shí)接口的 Waterful,就可以看到該接口的具體耗時(shí)。
可以看到,其耗時(shí)主要是在 Stalled,代表瀏覽器得到要發(fā)出這個(gè)請求的指令到請求可以發(fā)出的等待時(shí)間,一般是代理協(xié)商、以及等待可復(fù)用的 TCP 連接釋放的時(shí)間,不包括 DNS 查詢、建立 TCP 連接等時(shí)間等。
所以 API 一直在等待瀏覽器給它發(fā)出去的指令,以上面截圖的為例,整整等待了 23.84S,它請求和響應(yīng)的時(shí)間很快(最多也就幾百毫秒,也就是后端所說的接口并不慢)。
所以 API 到底在等待瀏覽器的什么處理?
什么阻塞了請求?
經(jīng)過定位,我們發(fā)現(xiàn),我們項(xiàng)目中使用 Server-Sent Events(以下簡稱 SSE)。它跟 WebSocket 一樣,都是服務(wù)器向?yàn)g覽器推送信息。但不同的是,它使用的是 HTTP 協(xié)議。
當(dāng)不通過 HTTP / 2 使用時(shí),SSE 會(huì)受到最大連接數(shù)的限制,限制為 6 次。此限制是針對每個(gè)瀏覽器 + 域的,因此這意味著您可以跨所有選項(xiàng)卡打開 6 個(gè) SSE 連接到 www.example1.com,并打開 6 個(gè) SSE 連接到 www.example2.com。這一點(diǎn)可以通過以下這個(gè) demo 復(fù)現(xiàn)。
復(fù)制問題的步驟:
- 訪問http://ssebin.btubbs.com/multi/。
- 單擊添加計(jì)數(shù)器6或更多次。
- 嘗試打開另一個(gè)標(biāo)簽到同一地址。
結(jié)果是,第 6 次之后,SSE 請求一直無法響應(yīng),打開新的標(biāo)簽到同一個(gè)地址的時(shí)候,瀏覽器也無法訪問。
效果圖如下:
該問題在 Chrome[1] 和 Firefox[2] 中被標(biāo)記為“無法解決”。
至于偶現(xiàn),是因?yàn)榍岸碎_發(fā)者有時(shí)候用 Chrome 會(huì)打開了多個(gè)選項(xiàng)卡,每個(gè)選項(xiàng)卡都是同一個(gè)本地開發(fā)地址,就會(huì)導(dǎo)致達(dá)到 SSE 的最大連接數(shù)的限制,而它的執(zhí)行時(shí)間會(huì)很長,也就會(huì)阻塞其他的請求,一致在等待 SSE 執(zhí)行完。
所以解決的方法是什么?
解決方案
簡單粗暴的兩個(gè)方法
- 不要打開太多個(gè)選項(xiàng)卡。這樣就不會(huì)達(dá)到它的限制數(shù)。(因?yàn)槲覀円粋€(gè)選項(xiàng)卡只請求一個(gè) SSE)。
- 開發(fā)環(huán)境下,關(guān)閉該功能。
使用 HTTP / 2
使用 HTTP / 2 時(shí),HTTP 同一時(shí)間內(nèi)的最大連接數(shù)由服務(wù)器和客戶端之間協(xié)商(默認(rèn)為 100)。
這解釋了為什么我們 test 環(huán)境沒有問題,因?yàn)?test 環(huán)境用的是 HTTP / 2。而在開發(fā)環(huán)境中,我們使用的是 HTTP 1.1 就會(huì)出現(xiàn)這個(gè)問題。
那如何在開發(fā)環(huán)境中使用 HTTP / 2 呢?
我們現(xiàn)在在開發(fā)環(huán)境,大部分還是使用 webpack-dev-server 起一個(gè)本地服務(wù),快速開發(fā)應(yīng)用程序。在文檔中,我們找到 server[3] 選項(xiàng),允許設(shè)置服務(wù)器和配置項(xiàng)(默認(rèn)為 'http')。
只需要加上這一行代碼即可。
devServer: {
+ server: 'spdy',
port: PORT,
}
看看效果,是成功了的。
原理使用 spdy[4] 使用自簽名證書通過 HTTP/2 提供服務(wù)。需要注意的一點(diǎn)是:
該配置項(xiàng)在 Node 15.0.0 及以上的版本會(huì)被忽略,因?yàn)?spdy 在這些版本中不會(huì)正常工作。一旦 Express 支持 Node 內(nèi)建 HTTP/2,dev server 會(huì)進(jìn)行遷移。
總結(jié)歸納
原本這個(gè)問題認(rèn)為跟前端無關(guān),沒想到最后吃瓜吃到自己頭上。提升相關(guān)技能的知識儲(chǔ)備以及思考問題的方式,可能會(huì)方便我們定位到此類問題。
充分利用好瀏覽器的調(diào)試工具,對一個(gè)問題可以從多個(gè)角度出發(fā)進(jìn)行思考。比如一開始,沒想到本地也可以開啟 HTTP / 2。后來偶然間想搜下是否有此類方案,結(jié)果還真有!
參考資料
[1]Chrome: https://bugs.chromium.org/p/chromium/issues/detail?id=275955。
[2]Firefox: https://bugzilla.mozilla.org/show_bug.cgi?id=906896。
[3]server: https://webpack.docschina.org/configuration/dev-server/#devserverserver。
[4]spdy: https://www.npmjs.com/package/spdy。