为何在页面上操做几回以后就变得奇慢无比,接口长时间处于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问题解析
相关文章
相关标签/搜索