当响应为异步时,Node.js 中如何处理多个并发请求?

How multiple simultaneous requests are handled in Node.js when response is async?

我可以想象 100 个请求到达单个 Node.js 服务器的情况。它们中的每一个都需要一些数据库交互,这些交互是通过一些本机异步代码实现的——使用任务队列或至少是微任务队列(例如,数据库驱动程序接口是 promisified)。

当请求处理程序停止同步时,Node.js return 如何响应?来自描述中的这 100 个请求的 api/web 客户端的连接发生了什么?

此功能在 OS 级别可用,称为(足够有趣)异步 I/O 或非阻塞 I/O(Windows 也 calls/called 它重叠 I/O).

在最低级别,在 C (C#/Swift) 中,操作系统提供了一个 API 来跟踪请求和响应。根据您使用的 OS 和 Node.js 使用 libuv to automatically select the best available API at compile time but for the sake of understanding how asynchronous API works let's look at the API that is available to all platforms: the select() 系统调用,有多种 API 可用。

select() 函数看起来像这样:

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, time *timeout);

fd_set 数据结构是您感兴趣的 set/list 个文件描述符 watching for I/O activity .请记住,在 POSIX 中,套接字也是文件描述符。这个API的使用方法如下:

// Pseudocode:

// Say you just sent a request to a mysql database and also sent a http
// request to google maps. You are waiting for data to come from both.
// Instead of calling `read()` which would block the thread you add
// the sockets to the read set:

add mysql_socket to readfds
add maps_socket to readfds

// Now you have nothing else to do so you are free to wait for network
// I/O. Great, call select:

select(2, &readfds, NULL, NULL, NULL);

// Select is a blocking call. Yes, non-blocking I/O involves calling a
// blocking function. Yes it sounds ironic but the main difference is
// that we are not blocking waiting for each individual I/O activity,
// we are waiting for ALL of them

// At some point select returns. This is where we check which request
// matches the response:

check readfds if mysql_socket is set {
    then call mysql_handler_callback()
}

check readfds if maps_socket is set {
    then call maps_handler_callback()
}

go to beginning of loop

所以基本上你的问题的答案是我们检查一个数据结构什么 socket/file 刚刚触发了 I/O activity 并执行适当的代码。

毫无疑问,您可以轻松发现如何概括此代码模式:无需手动设置和检查文件描述符,您可以将所有待处理的异步请求和回调保存在列表或数组中,并在 select()。这实际上就是 Node.js(通常 javascript)所做的。正是这个 callbacks/file-descriptors 的列表有时被称为事件队列 - 它本身并不是一个队列,只是您正在等待执行的事情的集合。

select()函数最后还有一个超时参数,可以用来实现setTimeout()setInterval(),在浏览器中处理GUI事件,这样我们就可以运行 代码等待 I/O。因为请记住,select 是阻塞的——如果 select returns,我们只能 运行 其他代码。通过仔细管理计时器,我们可以计算出适当的值作为超时传递给 select.

fd_set数据结构实际上并不是一个链表。在旧的实现中,它是一个位域。更现代的实现可以在位域上进行改进,只要它符合 API。但这部分解释了为什么有这么多相互竞争的异步 API,如 pollepollkqueue 等。它们的创建是为了克服 select 的局限性。不同的 APIs 以不同的方式跟踪文件描述符,一些使用链表,一些哈希表,一些迎合可扩展性(能够监听数万个套接字),一些迎合速度,大多数尝试这样做两者都比其他人好。无论他们使用什么,最终用于存储请求的只是一个跟踪文件描述符的数据结构