节点js架构和性能

问题描述 投票:6回答:1

我对Node js的架构和性能有疑问。

我已经完成了一些关于这个主题的阅读(包括Stack Overflow),我还有几个问题。我想做两件事:

  1. 总结一下我从简明地抓取许多不同来源所学到的知识,看看我的结论是否正确。
  2. 问一些关于Node的线程和性能的问题,我无法从我的研究中找出确切的答案。

Node具有单线程,异步事件处理架构

单线程 - 有一个事件线程调度异步工作(结果通常是I / O但可以是计算)并执行回调执行(即处理异步工作结果)。

  • 事件线程在无限的“事件循环”中运行,执行上述2个作业; a)通过调度异步工作来处理请求,以及b)注意先前的异步工作结果已准备好并执行回调以处理结果。
  • 这里的常见类比是餐馆订单接受者:活动线程是一个超级快速的服务员,从餐厅接受订单(服务请求)并将订单发送到厨房准备(发送异步工作),但也注意到当食物准备就绪(异步结果)并将其送回表格(回调执行)。
  • 服务员不做任何食物;他的工作是尽快从餐厅到厨房来回走动。如果他陷入餐厅的订单陷入困境,或者如果他被迫回到厨房准备其中一餐,系统就变得效率低下并且系统吞吐量受损。

异步由请求(例如Web请求)产生的异步工作流在逻辑上是一个链:例如

   FIRST [ASYNC: read a file, figure out what to get from the database] THEN 
   [ASYNC: query the database] THEN 
   [format and return the result].

上面标有“ASYNC”的作品是“厨房工作”,“FIRST []”和“THEN []”代表服务员开始回调的参与。

像这样的链以3种常见方式以编程方式表示:

  • 嵌套函数/回调
  • 用.then()链接的承诺
  • async()异步结果的异步方法。

所有这些编码方法都非常相同,尽管async / await似乎是最干净的,并且使得异步编码的推理更容易。

这是我对正在发生的事情的心理描述......这是正确的吗?评论非常感谢!

问题

我的问题涉及使用操作系统支持的异步操作,实际执行异步工作的人,以及这种体系结构比“按每个请求生成一个线程”(即多个厨师)体系结构更高效的方式:

  1. 通过使用跨平台异步库libuv设计节点库是异步的,对吗?这里的想法是libuv为节点(在所有平台上)提供一致的异步I / O接口,但随后使用平台相关的异步I / O操作吗?在I / O请求“一直向下”到OS支持的异步操作的情况下,谁正在“做工作”等待I / O返回并触发节点?它是内核,使用内核线程吗?如果不是,谁?无论如何,这个实体可以处理多少个请求?
  2. 我已经读过libuv也在内部使用了一个线程池(通常是pthreads,每个核心一个?)。这是为了“包装”不像“async”那样“完全向下”的操作,这样一个线程就可以用来等待同步操作了,所以libuv可以提供一个异步API吗?
  3. 关于性能,用于解释类似节点的体系结构可以提供的性能提升的通常说明是:画出(可能更慢和更胖)线程每请求方法 - 产生的延迟,CPU和内存开销一堆线程只是坐在等待I / O完成(即使他们没有忙碌等待)然后将它们拆除,节点很大程度上让它消失了,因为它使用了一个长期存在的事件线程来将异步I / O发送到OS /内核,对吗?但是在一天结束的时候,SOMETHING正在睡在一个互斥锁上并且在I / O准备就绪时被唤醒......是否认为如果它的内核比用户空间线程更有效?最后,请问由libuv的线程池处理的情况怎么样...这似乎与每个请求线程的方法类似,除了使用池的效率(避免启动和拆除),但是在这种情况下,当有很多请求并且池有积压时会发生什么?...延迟增加,现在你做的比每个请求的线程更糟,对吧?
javascript node.js multithreading asynchronous async-await
1个回答
3
投票

这里有很好的答案,可以让你更清楚地了解架构。但是,您有一些可以回答的具体问题。

谁在做“等待I / O返回并触发节点的工作”?它是内核,使用内核线程吗?如果不是,谁?无论如何,这个实体可以处理多少个请求?

实际上,线程和异步I / O都在同一个原语之上实现:OS事件队列。

发明多任务操作系统是为了允许用户使用单个CPU内核并行执行多个程序。是的,当时确实存在多核,多线程系统,但它们很大(通常是两三个普通卧室的大小)而且价格昂贵(通常是一两个普通房屋的成本)。这些系统可以在没有OS帮助的情况下并行执行多个操作。您只需要一个简单的加载程序(称为执行程序,类似于原始DOS的操作系统),您可以在没有操作系统帮助的情况下直接在程序集中创建线程。

更便宜,更大规模生产的计算机一次只能运行一件事。很长一段时间,用户都可以接受。然而,习惯于分时系统的人们希望从他们的计算机中获得更多。因此发明了过程和线程。

但在操作系统级别没有线程。操作系统本身提供了线程服务(从技术上讲,您可以在不需要操作系统支持的情况下将线程实现为库)。那么OS如何实现线程呢?

中断。它是所有异步处理的核心。

进程或线程只是一个等待CPU处理并由OS管理的事件。这是可能的,因为CPU硬件支持中断。等待I / O事件(来自鼠标,磁盘,网络等)的任何线程或进程都将停止,挂起并添加到事件队列中,并在等待时间内执行其他进程或线程。 CPU内置的定时器可以触发中断(令人惊讶的是,中断称为定时器中断)。此计时器中断会触发操作系统的进程/线程管理系统,这样即使它们都没有等待I / O事件,您仍然可以并行运行多个进程。

这是多任务处理的核心。这种编程(使用定时器和中断)通常不会被教授,除了操作系统设计,嵌入式编程(你经常需要在没有操作系统的情况下做类似OS的事情)和实时编程。

那么,异步I / O和进程之间的区别是什么?

除了操作系统向程序员公开的API之外,它们完全相同:

  • 进程/线程:嘿有程序员,假装你正在为一个CPU编写一个简单的程序,假装你可以完全控制CPU。来吧,使用我的I / O.当我处理并行运行的混乱时,我会保持控制CPU的错觉。
  • 异步I / O:你认为你比我更了解?好的,我允许您将事件侦听器直接添加到我的内部队列。但是我不打算在事件发生时处理调用哪个函数。我只是粗暴地唤醒你的过程,你自己处理所有这些。

在多核CPU的现代世界中,操作系统仍然进行这种过程管理,因为典型的现代操作系统运行许多过程,而PC通常只有两个或四个核心。使用多核机器还有另一个不同之处:

  • 进程/线程:因为我正在为你处理进程队列,我想你不介意我分散线程的负载你要求我运行几个CPU吗?这样我就可以让硬件并行工作了。
  • 异步I / O:抱歉,我无法在不同的CPU上传播所有不同的等待回调,因为我不知道你的代码到底在做什么。单核为您服务!

我已经读过libuv也在内部使用了一个线程池(通常是pthreads,每个核心一个?)。这是“包装”操作,而不是“一直向下”作为异步

是。

实际上,据我所知,所有操作系统都提供了足够好的异步I / O接口,您不需要线程池。自80年代以来,编程语言Tcl一直在处理类似节点的异步I / O,而没有线程池的帮助。但它非常混乱而且不那么简单。节点开发人员决定他们不想在磁盘I / O方面处理这种混乱,只需使用经过良好测试的阻塞文件API和线程。

但是在一天结束的时候,SOMETHING正在睡在互斥锁上并在I / O准备就绪时被唤醒

我希望我对(1)的回答也回答了这个问题。但是如果你想知道那是什么东西我建议你读一下C中的select()函数。如果你知道C编程我建议你尝试使用select()编写没有线程的TCP / IP程序。谷歌“选择c”。在另一个答案中,我有一个更详细的解释,说明这一切是如何在C级工作的:I know that callback function runs asynchronously, but why?

当有很多请求并且池有积压时会发生什么?...延迟增加,现在你的情况比每个请求的线程更糟,对吧?

我希望一旦你理解了我的回答(1)你也会意识到即使你使用线程也不会逃避积压。硬件实际上并不支持OS级别的线程。硬件线程仅限于内核数量,因此在硬件级别CPU是一个线程池。多线程中单线程之间的区别仅仅是多线程程序可以在硬件中并行执行多个线程,而单线程程序只能使用一个CPU。

异步I / O和传统多线程程序之间唯一真正的区别是线程创建延迟。从这个意义上说,没有像node.js这样的优势程序,而是使用像nginx和apache2这样的线程池的程序。

但是,由于CGI的工作方式,node.js之类的程序仍然具有更高的吞吐量,因为您不必为每个请求重新初始化解释器和程序中的所有对象。这就是为什么大多数语言已经转移到作为HTTP服务(如节点的Express.js)或类似FastCGI运行的Web框架的原因。


注意:你真的想知道线程创建延迟有什么大不了的吗?在90年代末/ 21世纪初,有一个Web服务器基准测试。 Tcl,一种比C平均低500%的语言(因为它基于像bash这样的字符串处理)设法胜过apache(这是在apache2之前并触发了创建apache2的完整重新架构)。原因很简单:tcl具有良好的异步I / O api,因此程序员更有可能使用异步I / O.这单独击败了用C编写的程序(不是C没有异步I / O,tcl毕竟是用C编写的)。

node.js相对于Java等语言的核心优势并不在于它具有异步I / O.这是异步I / O普遍存在且API(回调,承诺)易于使用,因此您可以使用异步I / O编写整个程序,而无需下拉到程序集或C.

如果您认为回调难以使用,我强烈建议您尝试在C中编写基于select()的程序。

© www.soinside.com 2019 - 2024. All rights reserved.