Node.js和CPU密集型请求


215

我已经开始尝试使用Node.js HTTP服务器,并且真的很想编写服务器端Javascript,但是有些事情使我无法开始在Web应用程序中使用Node.js。

我了解整个异步I / O概念,但我对程序代码占用大量CPU资源的极端情况(如图像处理或对大型数据集进行排序)感到有些担忧。

据我了解,对于简单的网页请求,例如查看用户列表或查看博客帖子,服务器将非常快。但是,如果我想编写非常占用CPU的代码(例如在管理后端),以生成图形或调整成千上万张图像的大小,则请求将非常缓慢(几秒钟)。由于此代码不是异步的,因此在那几秒钟内到达服务器的每个请求都将被阻止,直到我的慢请求完成为止。

一种建议是使用Web Workers执行CPU密集型任务。但是,恐怕网络工作者会很难编写干净的代码,因为它可以通过包含一个单独的JS文件来工作。如果CPU密集型代码位于对象的方法中怎么办?为每个CPU密集型方法编写JS文件实在是太糟了。

另一个建议是产生一个子进程,但这会使代码的可维护性降低。

有什么建议可以克服这个(公认的)障碍?在确保CPU重任务异步执行的同时,如何使用Node.js编写干净的面向对象的代码?


2
Olivier,您问了我想到的相同问题(对Node来说是新问题),尤其是在处理图像方面。在Java中,我可以使用固定线程的ExecutorService并将所有调整大小的作业传递给它,然后等待它完成所有连接,在节点中,我还没有弄清楚如何将工作改编到有限制的外部模块上(让我们例如)一次最多可同时进行2次操作。您找到了一种优雅的方法吗?
里亚德·卡拉

Answers:


55

您需要的是一个任务队列!将长时间运行的任务移出Web服务器是一件好事。将每个任务保存在“单独的” js文件中可促进模块化和代码重用。它迫使您考虑如何以某种方式构造您的程序,从长远来看,它将使调试和维护变得更加容易。任务队列的另一个好处是可以用不同的语言编写工作人员。只需弹出一个任务,执行工作,然后将响应写回即可。

像这样的东西https://github.com/resque/resque

这是github上的一篇文章,介绍了他们为什么建立它http://github.com/blog/542-introducing-resque


35
为什么要在节点世界中专门提出的问题中链接到Ruby库?
乔纳森·杜马因

1
@JonathanDumaine这是一个很好的任务队列实现。Rad Ruby代码并用javascript重写。利润!
Simon Stender Boisen

2
我对此非常钟爱齿轮工,齿轮工不会向齿轮工服务器轮询新工作-新工作会立即推送给工人。反应
Casey Flynn 2013年

1
实际上,有人将其移植到了节点世界:github.com/technoweenie/coffee-resque
FrontierPsycho

@pacerier,为什么这么说?你有什么建议?
luis.espinal

289

这是对Web服务器定义的误解-只能用于与客户端“对话”。重负载任务应委托给独立程序(当然也可以用JS编写)。
您可能会说它很脏,但是我向您保证,调整图像大小的Web服务器进程会更糟(即使可以说Apache,但它不会阻止其他查询)。不过,您可以使用公共库来避免代码冗余。

编辑:我想出了一个比喻;Web应用程序应作为餐厅。您有服务员(Web服务器)和厨师(工人)。服务员与客户保持联系,并执行一些简单的任务,例如提供菜单或解释某些菜是否素食。另一方面,他们将更艰巨的任务委托给厨房。因为服务员只是在做简单的事情,所以他们会迅速做出反应,而厨师则可以专心工作。

这里的Node.js将是一个单一但非常有才华的服务员,它可以一次处理许多请求,而Apache将是一群笨拙的服务员,每个服务员仅处理一个请求。如果这个Node.js服务员开始做饭,那将是一场灾难。尽管如此,烹饪还可能耗尽甚至大量的Apache服务员,更不用说厨房的混乱和响应能力的逐渐下降。


6
好吧,在Web服务器是多线程或多进程并且可以处理多个并发请求的环境中,在单个请求上花费几秒钟是很常见的。人们已经期望到了。我会误会是node.js是一个“常规” Web服务器。使用node.js,您必须稍微调整编程模型,其中包括将“长时间运行”的工作推送给某些异步工作程序。
Thilo

13
不要为每个请求都生成一个子进程(这违反了node.js的目的)。仅从您的繁重请求中产生工作者。或将大量的后台工作路由到除node.js之外的其他内容。
Thilo

47
打个比方,mbq!
Lance Fisher

6
哈,我真的很喜欢。“ Node.js:使不良做法
无法

7
@mbq我喜欢类比,但它可能需要一些工作。传统的多线程模型将既是服务员又是厨师。一旦点了菜,那个人必须回去煮饭,然后才能处理另一个菜。node.js模型将节点作为服务员,将Webworkers作为厨师。服务员负责处理请求/解决请求,而工作人员则负责处理更耗时的任务。如果需要扩大规模,则只需将主服务器设置为节点群集,然后将CPU密集型任务反向代理到为多线程处理而构建的其他服务器。
Evan Plaice 2012年

16

您不希望您的CPU密集型代码执行异步,而是希望它并行执行。您需要从处理HTTP请求的线程中删除处理工作。这是解决此问题的唯一方法。使用NodeJS,答案就是集群模块,用于产生繁重的子进程。(AFAIK Node没有线程/共享内存的任何概念;它是进程或什么都没有)。对于如何构建应用程序,您有两个选择。通过产生8个HTTP服务器并在子进程上同步处理计算密集型任务,可以获得80/20解决方案。这样做很简单。您可能需要一个小时来阅读该链接。实际上,如果仅撕掉该链接顶部的示例代码,您将获得95%的访问权限。

构造此任务的另一种方法是设置作业队列并通过该队列发送大型计算任务。请注意,作业队列的IPC有很多开销,因此仅当任务明显大于开销时,这才有用。

令我惊讶的是,这些其他答案都没有提到集群。

背景:异步代码是暂停执行直到其他地方发生事件的代码,这时代码醒来并继续执行。I / O是一种很常见的情况,必须在其他地方发生缓慢的情况。

如果由您的处理器来负责工作,那么异步代码就没有用。“计算密集型”任务就是这种情况。

现在,异步代码似乎很合适,但实际上它很常见。它恰好对计算密集型任务没有用。

例如,等待I / O是Web服务器中经常发生的一种模式。连接到您的服务器的每个客户端都有一个套接字。大多数时候,插座是空的。在套接字接收到一些数据之前,您不需要执行任何操作,此时您要处理请求。在后台,像Node这样的HTTP服务器正在使用事件库(libev)来跟踪数千个打开的套接字。操作系统通知libev,然后在套接字之一获取数据时,libev通知NodeJS,然后NodeJS在事件队列中放置一个事件,此时您的http代码开始处理,并依次处理事件。在套接字具有一些数据之前,事件不会进入队列,因此事件永远不会在数据上等待-它已经在等待数据了。

当瓶颈正在等待一堆几乎是空的套接字连接并且您不想为每个空闲连接使用整个线程或进程并且您不想轮询250k时,基于事件的单线程Web服务器就成为一种范式套接字以查找下一个包含数据的套接字。


应该是正确的答案...。对于产生8个群集的解决方案,您需要8个核心,对吗?或具有多个服务器的负载均衡器。
穆罕默德·乌默

还有什么是学习第二种解决方案(设置队列)的好方法。队列的概念非常简单,但是它是进程与外部队列之间的消息传递部分。
穆罕默德·乌默

那就对了。您需要以某种方式使工作进入另一个核心。为此,您需要另一个核心。
masonk '18年

回复:队列。实际的答案是使用作业队列。有一些可用于节点。我从未使用过它们,因此无法提出建议。出于好奇,答案是工作进程和队列进程最终将通过套接字进行通信。
masonk '18年

7

您可以使用几种方法。

如@Tim所述,您可以创建一个异步任务,该任务位于主服务逻辑之外或与之并行。取决于您的确切要求,但是即使cron也可以充当排队机制。

WebWorkers可以为您的异步进程工作,但是node.js当前不支持它们。有几个提供支持的扩展,例如:http : //github.com/cramforce/node-worker

您仍然可以通过标准的“需要”机制重用模块和代码。您只需要确保向工作人员的初始调度传递了处理结果所需的所有信息。


0

使用child_process是一种解决方案。但是与Go相比,产生的每个子进程可能会消耗大量内存goroutines

您还可以使用基于队列的解决方案,例如kue

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.