为什么setTimeout会阻塞我的Node.js应用程序?

15

请看这段代码,典型的Node.js Http服务器示例。我在其中添加了5秒钟的延迟,以模拟某个其他地方正在进行异步工作:

const http = require('http');

const hostname = '127.0.0.1';
const port = 8080;

http.createServer((req, res) => {
  setTimeout(()=>{
      res.writeHead(200, { 'Content-Type': 'text/plain' });
      res.end('Hello World\n');
  },5000);
}).listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});

我希望的是,当我打开5个标签页时,每个标签页之间相隔半秒钟,服务器应该以大致如下的时间响应每个标签页:
t=0s - I open first tab
t=0.5s - I open second tab
t=1s - I open third tab
...
t=5s - First tab stops loading, server has replied
t=5.5s - Second tab stops loading, server has replied
t=6s - Third tab stops loading, server has replied
t=6.5s - Fourth tab stops loading, server has replied
t=7s - Fifth tab stops loading, server has replied

然而,我所看到的行为是以下这样的:
t=0s - I open first tab
t=0.5s - I open second tab
t=1s - I open third tab
...
t=5s - First tab stops loading, server has replied
t=10s - Second tab stops loading, server has replied
t=15s - Third tab stops loading, server has replied
t=20s - Fourth tab stops loading, server has replied
t=25s - Fifth tab stops loading, server has replied

似乎后续请求要等待第一个请求完成后才开始运行。我有什么遗漏吗?我认为 Node JS 的整个意义在于能够从单线程运行异步任务,您觉得呢?

1
顺便提一下,您还需要注意浏览器在页面请求时喜欢发出的网站图标请求,这也可能会使事情变得更加复杂。 - jfriend00
2个回答

20

问题不在于你的代码或Node.js——而是你如何设置测试。

你错误地假设你的浏览器会同时发出5个请求,但这并没有发生。不同的浏览器有不同的行为,但通常情况下,浏览器将单个来源的最大同时连接数限制得非常低。HTTP规范给出了建议的最大值。实际上,我很惊讶地看到Chrome只打开了一个与本地主机的连接,因为我知道Chrome会向其他来源打开6个连接——我学到了新东西!

使用不同的工具运行你的测试,一个你可以控制并确信它正在进行并发请求的工具。然后你将看到预期的行为。

例如,我使用Apache Benchmark运行了你的代码,并按下面所示进行了测试。参数指示:-n 10表示进行10个请求,-c 10表示使用并发量10(即所有十个请求同时进行)。正如你在下面的结果中所看到的,所有请求所花费的总时间约为5秒(每个请求的时间为0.5秒):

~ $ ab -n 10 -c 10 http://127.0.0.1:8080/
This is ApacheBench, Version 2.3 <$Revision: 1663405 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 127.0.0.1 (be patient).....done


Server Software:
Server Hostname:        127.0.0.1
Server Port:            8080

Document Path:          /
Document Length:        12 bytes

Concurrency Level:      10
Time taken for tests:   5.019 seconds
Complete requests:      10
Failed requests:        0
Total transferred:      1130 bytes
HTML transferred:       120 bytes
Requests per second:    1.99 [#/sec] (mean)
Time per request:       5019.151 [ms] (mean)
Time per request:       501.915 [ms] (mean, across all concurrent requests)
Transfer rate:          0.22 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.1      0       0
Processing:  5017 5018   0.3   5018    5018
Waiting:     5008 5008   0.2   5008    5009
Total:       5018 5018   0.2   5019    5019
ERROR: The median and mean for the total time are more than twice the standard
       deviation apart. These results are NOT reliable.

Percentage of the requests served within a certain time (ms)
  50%   5019
  66%   5019
  75%   5019
  80%   5019
  90%   5019
  95%   5019
  98%   5019
  99%   5019
 100%   5019 (longest request)

4
请问需要翻译的内容是:“The OP can also look in the network tab of the Chrome debugger to see when the requests are actually sent to the server. The timeline in the network tab of the debugger there will tell the WHOLE story.” ? - jfriend00
先生说得非常正确,我尝试在Chrome、Firefox和Edge三个浏览器中同时打开并刷新页面,结果如预期一样!感谢您抽出时间进行测试并发布结果!太棒了,这就是我喜欢Stack Overflow的原因 :) - Ruben Serrate
@jfriend00 是的,没错,我甚至没有想到这可能是浏览器问题! - Ruben Serrate
@RubenSerrate - 是的,浏览器会尝试保护网页对任何给定主机的同时负载。我不确定整个历史背景,但每个浏览器都为此编码了连接限制,如果您没有意识到这是正在发生的事情,它可能会让您自己的测试非常困惑。 - jfriend00
只有一个并发连接,因为所有的连接都指向相同的URL,而不是因为它在本地主机上。 - John Dvorak

5

似乎这只发生在某些浏览器中,我用Safari尝试了一下,结果符合预期。因此我猜测Chrome会限制同时发送到同一资源的相同请求的数量。


Firefox 也在这方面做出了相应的措施(阻止向同一主机建立过多连接)。 - Ruben Serrate

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接