Pra*_*mar 5 javascript event-loop node.js promise async-await
我试图了解 node.js 单线程架构和事件循环,以使我们的应用程序更高效。因此,请考虑这种情况,我必须为 http api 调用进行多次数据库调用。我可以使用Promise.all()
或使用单独的await
.
例子:
使用异步/等待
await inserToTable1();
await insertToTable2();
await updateTable3();
Run Code Online (Sandbox Code Playgroud)
使用Promise.all()
我可以做同样的事情
await Promise.all[inserToTable1(), insertToTable2(), updateTable3()]
Run Code Online (Sandbox Code Playgroud)
这里对于给定时间的一个 API 命中,Promise.all()
将更快地返回响应,因为它并行触发 DB 调用。但是,如果我每秒有 1000 个 API 命中,会有什么不同吗?对于这种情况,事件Promise.all()
循环更好吗?
更新 假设如下,通过 1000 个 API 命中,我指的是应用程序的总体流量。考虑有 20-25 个 API。其中一些可能会执行数据库操作,一些可能会进行一些 http 调用等。此外,我们永远不会达到数据库池的最大连接数。
提前致谢!!
像往常一样,在系统设计方面,答案是:视情况而定。
有很多因素决定了两者的性能。通常,等待单个Promise.all()
并行等待所有请求。
事件循环正好使用 0% 的 CPU 时间来等待请求。请参阅我对这个相关问题的回答以了解事件循环的工作原理:具有大量回调的 NodeJS 的性能
因此,从事件循环的角度来看,顺序请求和并行请求之间没有真正的区别Promise.all()
。因此,如果这是您问题的核心,我想答案是两者之间没有区别。
但是,处理回调确实需要 CPU 时间。同样,完成执行所有回调的时间是相同的。所以再次从CPU性能的角度来看,两者没有区别。
然而,并行发出请求确实减少了整体执行时间。首先,如果服务是多线程的,您实际上是通过发出并行请求来使用它的多线程性。这就是使 node.js 运行速度快的原因,即使它是单线程的。
即使您请求的服务不是多线程的并且实际上按顺序处理请求,或者如果您请求的服务器是单核 CPU(现在很少见,但您仍然可以租用单核虚拟机)然后并行请求减少网络开销,因为您的操作系统可以在单个以太网帧中发送多个请求,从而在多个请求上分摊数据包标头的开销。然而,除了大约六个并行请求之外,这确实有一个递减的回报。
您假设发出 1000 个请求。天气或不并行等待 1000 个承诺实际上会导致并行请求取决于 API 在网络级别的工作方式。
许多数据库库都实现了连接池。也就是说,库将打开一定数量的数据库连接,例如 5 个,并重用这些连接。
在某些实现中,通过这样的库发出 1000 个请求将导致库的低级网络代码一次批处理 5 个请求。这意味着您最多可以有 5 个并行请求(假设池中有 5 个连接)。在这种情况下,发出 1000 个并行请求是完全安全的。
然而,一些实现具有可增长的连接池。在此类实现中,发出 1000 个并行请求将导致您的软件打开 1000 个套接字以访问远程资源。在这种情况下,发出 1000 个并行请求的安全性取决于远程服务器允许这样做的天气。
大多数数据库(例如 Mysql 和 Postgresql)允许管理员配置连接限制,例如 5,这样数据库将拒绝超过每个 IP 地址的有限连接数。如果您使用的库不会自动管理与数据库的最大连接数,那么您的数据库将接受前 5 个请求并拒绝剩余的请求,直到另一个插槽可用(可能在 node.js 完成打开第 1000 个套接字之前释放了连接)。在这种情况下,您无法成功发出 1000 个并行请求 - 您需要管理发出的并行请求数量。
某些 API 服务还限制您可以并行建立的连接数。例如,谷歌地图将您限制为每秒 500 个请求。因此,等待 1000 个并行请求将导致 50% 的请求失败,并可能导致您的 API 密钥或 IP 地址被禁止。
您的机器或服务器可以打开的套接字数量存在理论上的限制。然而,这个数字非常高,所以这里不值得讨论。
但是,当前存在的所有操作系统都限制了打开套接字的最大数量。在 Linux(例如 Ubuntu 和 Android)和 Unix(例如 MacOSX 和 iOS)上,套接字被实现为文件描述符。每个进程分配的文件描述符有最大数量。
对于 Linux,这个数字通常默认为 1024 个文件。请注意,进程默认打开 3 个文件描述符:stdin、stdout 和 stderr。这留下了由文件和套接字共享的 1021 个文件描述符。所以你的 1000 个并行请求非常接近这个数字,如果两个客户端尝试同时发出 1000 个并行请求,可能会失败。
这个数字可以增加,但它确实有一个硬性限制。当前您可以在 Linux 上配置的最大文件描述符数是 590432。然而,这种极端配置只能在没有守护进程(或其他后台程序)运行的单用户系统上正常工作。
编写网络代码时的第一条规则是尽量不要破坏网络。在任何时候提出的请求数量要合理。您可以将请求批处理到服务期望的限制。
使用 async/await 很容易。你可以这样做:
let parallel_requests = 10;
while (one_thousand_requests.length > 0) {
let batch = [];
for (let i=0;i<parallel_requests;i++) {
let req = one_thousand_requests.pop();
if (req) {
batch.push(req());
}
}
await Promise.all(batch);
}
Run Code Online (Sandbox Code Playgroud)
通常,您可以并行发出的请求越多,整体处理时间就越好(更短)。我想这就是你想听到的。但是您需要平衡并行性与上述因素。5 一般是可以的。10 也许。100 将取决于响应请求的服务器。1000 或更多,安装服务器的管理员可能需要调整他的操作系统。
await
方法将暂停每次调用的函数执行await
并按顺序执行它们,同时Promise.all
可以并行(异步)执行事物,并在所有调用都成功时返回成功。
Promise.all
因此,如果您的三个 ( inserToTable1()
, insertToTable2()
, table3()
) 方法是独立的,那么最好使用。
javascript 在发生繁重操作时通过挂起执行其他内容的能力是通过事件循环和调用堆栈实现的。
\n调用者与响应的解耦允许 JavaScript 运行时在等待异步操作完成及其回调触发时执行其他操作。
\nJavaScript 运行时包含一个消息队列,该队列存储要处理的消息列表及其关联的回调函数。如果已提供回调函数,这些消息将排队以响应外部事件(例如单击鼠标或接收对 HTTP 请求的响应)。
\n事件循环有一个简单的作业 \xe2\x80\x94 来监视调用堆栈和回调队列。如果调用堆栈为空,它将从队列中取出第一个事件并将其推送到调用堆栈,调用堆栈将有效地运行它。
\n