Node.js单线程模型如何处理并发请求?

3

两个并发请求R1和R2到达node.js服务器。Node.js使用单线程T1运行。 T1花费5秒钟处理请求R1。 假设这些时间用于某个大型功能的实际处理,并且没有等待/IO调用。

我的问题是 - 请求R2将在5秒钟后(一旦R1完成)被接管,还是两者(R1和R2)将轮流运行?

如果答案是顺序的(即R2将在5秒后被接管),我的后续问题是假设我有5k个HTTP并发请求,每个请求需要2毫秒,则最后一个请求将在5k * 2ms = 10秒后得到响应。 这不好吗? 我需要进行群集化吗?


Node 运行一个事件循环系统,而不是线程。每个请求触发事件循环,因此 R1 和 R2 并行运行。 - Marco Scabbiolo
那么这里的单线程模型是什么?你的意思是两个请求将触发两个事件,事件循环将通过单线程以轮询方式执行相应的事件处理程序,一旦完成就会发送响应回来吗? - user3198603
这是一个连续循环系统,而不是线程化的系统,请不要考虑线程,因为这是在你无法访问的较低级别上处理的,谢天谢地。两个请求将同时运行。 - Marco Scabbiolo
@MarcoScabbiolo 在发表评论之前请先进行实验。简单的谷歌搜索就可以告诉你Node.js是单线程的。如果第一个请求被阻塞,那么这两个请求将不会并行运行。 - Tim Wong
@TimWong Node运行一个事件循环,它是一种不同类型的系统,实现方式不影响事件循环的工作,你不需要担心线程,只需要理解事件循环即可。当然,事件循环中的一个阻塞将会阻止整个事件循环的运行,这就是它的工作原理!如果你想了解更多信息,请访问https://www.youtube.com/watch?v=QgwSUtYSUqA。 - Marco Scabbiolo
@MarcoScabbiolo 没有人担心线程,但了解node.js并行性是真正的并行还是轮询方式非常重要。只需在Google上搜索node.js中的多进程即可。 - emrhzc
2个回答

2

如果R1是同步的,那么只有在R1完成后才会开始执行R2

简而言之,您可以搜索nodejs事件循环。有很多优秀的文章解释了Node.js如何使用事件循环来处理请求。

事件循环是Node.js实现非阻塞I/O操作的关键 —— 尽管JavaScript是单线程的 —— 它能够在可能的情况下将操作转移给系统内核。(来源:https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/)

您是正确的,Node.js是单线程的。如果长时间运行的任务导致其被阻塞,则无法处理任何其他内容。在您的情况下,您应该将R1拆分为较小的异步处理部分,或者您可以使用child_process将操作转移到另一个线程。

如果答案是顺序的(例如,R2将在5秒后开始),我的后续问题是,假设我有5k个HTTP并发请求,每个请求需要2毫秒,那么最后一个请求将在5k * 2ms = 10秒后被服务。这不好吗?我需要在这里进行集群吗?

这取决于情况。2毫秒实际上是计算机处理很多事情的时间较长。在进行集群之前,您应该像上面提到的那样重构代码以最小化请求处理程序中的阻塞代码。在为集群购买更多服务器之前,您可以使用cluster库将应用程序克隆到其他线程中,从而充分利用CPU核心。设计良好的Node.js应用程序应该能够处理数千个请求而没有问题。否则,您可能需要重新考虑是否Node.js是您的应用程序的最佳选择。

额外奖励:让我们听一下Node.js的发明人 - 他为什么创建了Node.js


“if R1 is synchronous” 是什么意思?这些是来自浏览器的 HTTP 请求,发送到 Node 服务器。那么这里的同步是什么意思? - user3198603
我的意思是你请求处理程序中的逻辑。例如,一个简单的while循环是同步的,因为在while循环完成之前应用程序无法执行其他操作。fs.readFile 操作是异步的,因为文件I/O在后台处理,应用程序能够同时处理其他请求。 - Tim Wong
谢谢,我明白了。关于“通过将应用程序克隆到其他线程来充分利用CPU核心”的问题。假设我的Node.js(单线程)正在8080端口上运行,这已经利用了一个核心。如果使用cluster库来利用另一个核心,它会启动另一个Node进程并在同一端口8080上进行监听吗? - user3198603
虽然我不太确定它是如何实现的,但官方文档中的示例说明工作进程可以共享任何TCP连接(例如,在您的情况下为8080端口)。请参考文档以获取更多解释。请注意,在集群应用程序时,您的应用程序应该是stateless,否则事情将变得不可预测。 - Tim Wong
上一个跟进问题,你说…或者你可以使用child_process将操作卸载到另一个线程中。我不确定你所说的“另一个线程”是什么意思?在node.js中能否有多个线程(因为node在单线程模型下不断处理来自事件循环的事件)? - user3198603
是的。更准确地说,child_process 创建了另一个进程,该进程有自己的线程。cluster 模块确实在幕后使用了 child_process - Tim Wong

1
如果您的函数是CPU绑定的,将会占用事件循环。你有两个选择。
  1. 切换到与其他进程不同的通信模型,可能运行它们自己的事件循环:预派生或工作器MPM。如果需要,您可以从Node内部生成这些进程使用child_process
  2. 限制CPU处理,并发出.nextTick()以允许事件循环中的其他事情完成并稍后恢复。

如果我不按照您提到的步骤进行操作,R2请求将何时被处理? 是在5秒钟之后(即顺序处理)吗? - user3198603
@user3198603 是的,只要没有发生客户端超时,这是很典型的情况。不会有太多客户端愿意等待五秒钟让你的服务器回答并开始处理(尽管网络浏览器会这样做)。 - Evan Carroll
你说“不会有太多客户愿意等待五秒钟来等待您的服务器回答并开始处理(尽管网络浏览器会这样做)”,我不是很明白。R2的浏览器/客户端会等待几秒钟,对吗? - user3198603

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