爲何在頁面上操做幾回以後就變得奇慢無比,接口長時間處於pending狀態?

開發環境

前端:Vue 2.0
後臺:Node Express
瀏覽器:Chrome
部署系統:Linux

問題現象

在現有項目的基礎之上增長了兩個頁面,可是在使用的過程當中發現,當連續操做幾回以後頁面會變得奇慢無比,查看接口調用發現接口請求長時間處於pending狀態,可是等1-2分鐘左右接口仍是會返回應答結果。以下圖所示:前端

圖片描述

緣由分析

經過反覆復現該問題(在各個頁面之間不一樣切換,觸發請求),發現了一個規律,就是每次在第7次頁面切換的時候,全部接口都會被阻塞並在1分多鐘以後才返回。node

圖片描述

看看這1分多鐘究竟花在了哪裏?git

圖片描述

從上圖能夠看到,整個接口請求的大部分時間都花在了Stalled階段。如今的問題是Stalled是啥意思?下面是一段比較淺顯的解釋:github

Time the request spent waiting before it could be sent. This time is inclusive of any time spent in proxy negotiation.Additionally, this time will include when the browser is waiting for an already established connection to become available for re-use, obeying Chrome’s maximum six TCP connection per origin rule.

從上面的解釋看,可能有兩個緣由:web

  1. TCP鏈接出問題了,一直沒法建鏈成功;
  2. TCP鏈接是OK的,可是一直被佔用沒法使用。

首先看第一個問題,TCP鏈接是否正常?chrome

$ lsof -i:8700
COMMAND   PID     USER   FD   TYPE    DEVICE SIZE/OFF NODE NAME
node    26315 mumingv   12u  IPv4 449215145     0t64  TCP *:8700 (LISTEN)
node    26315 mumingv   16u  IPv4 449217569     0t64  TCP localhost:8700->172.24.186.14:54064 (ESTABLISHED)
node    26315 mumingv   17u  IPv4 449217570     0t64  TCP localhost:8700->172.24.186.14:54065 (ESTABLISHED)
node    26315 mumingv   18u  IPv4 449217580     0t64  TCP localhost:8700->172.24.186.14:54066 (ESTABLISHED)
node    26315 mumingv   19u  IPv4 449217581     0t64  TCP localhost:8700->172.24.186.14:54067 (ESTABLISHED)
node    26315 mumingv   20u  IPv4 449226874     0t64  TCP localhost:8700->172.24.186.14:54574 (ESTABLISHED)
node    26315 mumingv   21u  IPv4 449217583     0t64  TCP localhost:8700->172.24.186.14:54069 (ESTABLISHED)

上圖中,8700是網站的服務端口號,172.24.184.14是Chrome瀏覽器所在Mac的IP。在復現問題的過程當中一直執行lsof -i:8700持續進行觀察發現,當在第7次頁面切換的時候,這裏的TCP鏈接數量再也不增長,維持在6個左右且狀態都是ESTABLISHED(已創建)。因此能夠基本排除TCP鏈接的問題。express

固然,也能夠經過netstat命令查詢TCP鏈接狀態。json

$ netstat -tunpa | grep 8700
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
tcp        0      0 0.0.0.0:8700                0.0.0.0:*                   LISTEN      27765/node          
tcp        0      0 10.95.199.140:8700          172.24.186.14:65413         ESTABLISHED 27765/node          
tcp        0      0 10.95.199.140:8700          172.24.186.14:65412         ESTABLISHED 27765/node          
tcp        0      0 10.95.199.140:8700          172.24.186.14:65421         ESTABLISHED 27765/node          
tcp        0      0 10.95.199.140:8700          172.24.186.14:65420         ESTABLISHED 27765/node          
tcp        0      0 10.95.199.140:8700          172.24.186.14:65419         ESTABLISHED 27765/node          
tcp        0      0 10.95.199.140:8700          172.24.186.14:65422         ESTABLISHED 27765/node

再來看第二個問題,TCP鏈接被誰佔用了不釋放?後端

看看是否是有其餘請求佔用了這些TCP鏈接,查看全部請求,果不其然:瀏覽器

圖片描述

原來每次在頁面切換的時候,瀏覽器都會默認發送一個請求獲取一次網頁圖標,這個不是前端業務邏輯主動調用的XHR請求,但對於後端來講也是一次GET請求。

實際上,若是沒有要求顯示特定網頁圖標的話,後端隨便返回一個信息就行了,不用非得準備一個網頁圖標。瀏覽器拿不到圖標的話會顯示一個默認圖標。

問題找到了,看看爲啥後端爲啥沒有返回圖標並加以解決就行了。具體到這個項目,是在node express的app.js入口文件中沒有註冊相應的處理邏輯。

// 接口路由
loadRouter(app, '/project-name', path.join(__dirname, 'app/controllers'));

// 靜態頁面
app.use('/project-name', express.static(path.join(__dirname, "webroot", "project-name")));

// favicon.ico和其餘不支持的請求
app.get("*", function(req, res) {
    if (req.path === "/favicon.ico") {
        return;  // !!!這裏不能直接return,須要返回具體的內容,不然會阻塞express框架返回應答消息!!!
    }   
    throw new PathError();
});

知道問題後,修改就很簡單了。

app.get("*", function(req, res) {
    if (req.path === "/favicon.ico") {
        res.json({'status':0, msg:''});  // 這裏隨便返回個內容就行,不影響瀏覽器使用默認圖標進行展現
    }   
    throw new PathError();
});

至此,問題解決。

FAQ

Q:爲何瀏覽器和服務端之間最多隻能建立6個TCP鏈接?

TCP鏈接資源數量有限,若是不限制數量的話,全部TCP所有被佔用的話系統就「沒法提供服務」了。通常瀏覽器的併發TCP鏈接數量都在五、6個左右,對於Chrome來講是6個。至於爲何是這麼多,這是各瀏覽器自行設置的,沒有標準。具體解釋參考:官方文檔

Q:後續如何排查這類接口問題?

通常按照以下幾步進行排查便可:

  1. 瀏覽器端看XHR請求,判斷XHR請求自己是否有異常;
  2. 瀏覽器端看ALL請求,判斷非XHR請求是否有異常;
  3. 服務器端查看服務自己是否正常;
  4. 服務器端查看服務創建的TCP鏈接是否正常;
  5. 抓包查看TCP交互和業務請求交互報文是否有異常。

參考資料

  1. Network Issues Guide
  2. Understanding Resource Timing
  3. chrome的timeline中stalled問題解析
相關文章
相關標籤/搜索