最近项目中出现一个问题,前端每隔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.
Stalled:在满足Queueing的任意一种条件时,请求将会停滞(阻塞)。
更高优先级的请求此时并不存在,排除;TCP连接数量限制此时也未达上限,而且同网站的其他请求并未受影响,排除(对于TCP连接和数量限制后续可以再研究一下);
同时在StackOverFlow上我找到一个类似的问题:https://stackoverflow.com/questions/27513994/chrome-stalls-when-making-multiple-requests-to-same-resource,其中一个回答如下:
Yes, 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
结果:bingo!问题解决,所有请求并发进行,不再阻塞!
那么能不能通过后端的响应来解决问题呢?于是又做了一些验证
验证2:修改响应头,试图控制浏览器的缓存行为
分别修改Cache-Control响应头的值为no-store/no-cache/no-store,no-cache/max-age=3, must-revalidate,观察
结果:前面3种毫无反应;对于第4种,结果如下:
没有缓存时,请求从后台获取数据,然后缓存到本地;有缓存时,请求直接从磁盘缓存获取数据,对于接口类的请求来说这有可能获取到国企的数据,显然是不可接受的,事实上,一般之后资源类的数据(js/css/图片等)才会通过缓存获取。
结论
在拿到响应之前,chrome会将资源(以url表示)相关的缓存锁住,后续所有相同的url请求都必须在队列中等待,直到前面的请求及缓存处理完之后才能依次进行。
要解决这个问题,只要每次请求时在链接上加个随机值就好了。
Tips
这次解决这个问题花的时间稍微有点长,原因是一开始的方向有误,直接去翻后台代码和脚本浪费了不少时间和精力。
正确的做法是首先确定问题发生在哪里:前端还是后端,如果是后端的话再看是服务器还是服务本身(老二分法了),确定是服务本身之后再去看细节,不花无谓的时间。
有话要说