首页 > 解决方案 > 单线程 Node.js 如何并发处理请求?

问题描述

我目前正在深入学习Nodejs平台。众所周知,Nodejs 是单线程的,如果它执行阻塞操作(例如 fs.readFileSync),线程应该等待完成该操作。我决定做一个实验:我创建了一个服务器,它在每次请求时从一个文件中响应大量数据

const { createServer } = require('http');
const fs = require('fs');

const server = createServer();

server.on('request', (req, res) => {
    let data;
    data =fs.readFileSync('./big.file');
    res.end(data);
});

server.listen(8000);

此外,我启动了 5 个终端,以便对服务器进行并行请求。我等着看,在处理一个请求时,其他请求应该等待从第一个请求完成阻塞操作。但是,其他 4 个请求同时响应。为什么会出现这种行为?

标签: javascriptnode.jssingle-threaded

解决方案


您可能看到的是内部实现的一些异步部分res.end()以实际发送大量数据,或者您看到所有数据都非常快速且连续地发送,但客户端无法快速处理它以实际连续显示它,并且因为客户端每个都在他们自己的单独进程中,所以它们“似乎”显示它同时到达只是因为它们反应太慢而无法显示实际到达顺序。

必须使用网络嗅探器来查看其中哪些实际发生或运行一些不同的测试,或者将一些日志记录放在实现中res.end()或利用客户端 TCP 堆栈中的一些日志记录,以确定不同数据包到达的实际顺序要求。


如果您有一台服务器并且它有一个正在执行同步 I/O 的请求处理程序,那么您将不会同时获得多个请求处理。如果您认为这种情况正在发生,那么您将必须准确记录您是如何衡量或得出结论的(这样我们可以帮助您消除误解),因为这不是 node.js 在使用阻塞、同步 I/O 等时的工作方式作为fs.readFileSync().

node.js 将您的 JS 作为单线程运行,当您使用阻塞、同步 I/O 时,它会阻塞 Javascript 的一个单线程。这就是为什么您永远不应该在服务器中使用同步 I/O,除非在启动代码中仅在启动期间运行一次。

很明显,这fs.readFileSync('./big.file')是同步的,因此在第一个请求完成之前,您的第二个请求不会开始处理fs.readFileSync()。而且,一遍又一遍地在同一个文件上调用它会非常快(操作系统磁盘缓存)。

但是,res.end(data)是非阻塞的,异步的。 res是一个流,你给流一些数据来处理。它会通过套接字发送尽可能多的内容,但如果它由 TCP 控制流量,它将暂停,直到套接字上有更多空间可以发送。发生多少取决于您计算机的各种情况,它的配置以及到客户端的网络链接。

所以,可能发生的是这一系列事件:

  1. 第一个请求到达并fs.readFileSync()调用res.end(data)。这开始向客户端发送数据,但由于 TCP 流控制,它在完成之前返回。这会将 node.js 发送回其事件循环。

  2. 第二个请求到达并fs.readFileSync()调用res.end(data)。这开始向客户端发送数据,但由于 TCP 流控制,它在完成之前返回。这会将 node.js 发送回其事件循环。

  3. 此时,事件循环可能会开始处理第三个或第四个请求,或者它可能会为更多事件提供服务(从res.end()第一个请求的实现或 writeStream 内部继续发送更多数据。如果它确实为这些事件提供服务,它可以给出不同请求的真正并发的外观(从客户端的角度)。

此外,客户端可能导致它显示为已排序。每个客户端都在读取不同的缓冲套接字,如果它们都在不同的终端中,那么它们是多任务的。因此,如果每个客户端的套接字上的数据多于它可以立即读取和显示的数据(可能就是这种情况),那么每个客户端将读取一些、显示一些、读取更多、显示更多等等......如果在您的服务器上发送每个客户端的响应之间的延迟小于在客户端上读取和显示的延迟,然后客户端(每个客户端都在自己的单独进程中)能够同时运行。


当您使用诸如 之类的异步 I/O 时fs.readFile(),正确编写的 node.js Javascript 代码可以同时有许多“正在运行”的请求。它们实际上并不是在完全相同的时间同时运行,但可以运行,做一些工作,启动异步操作,然后让位于让另一个请求运行。使用正确编写的异步 I/O,可以从外部世界看到并发处理,即使它更类似于在请求处理程序等待异步 I/O 请求完成时共享单个线程。但是,您展示的服务器代码不是这种协作的异步 I/O。


推荐阅读