Node.js和CPU密集型请求

时间:2010-08-16 09:04:32

标签: javascript node.js serverside-javascript web-worker

我已经开始修改Node.js HTTP服务器,并且非常喜欢编写服务器端Javascript,但是有些东西阻止我开始使用Node.js作为我的Web应用程序。

我理解整个异步I / O概念,但我有点担心程序代码非常耗费CPU的边缘情况,例如图像处理或排序大数据集。

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

一个建议是使用Web Workers进行CPU密集型任务。但是,我担心网络工作者会很难编写干净的代码,因为它的工作方式是包含一个单独的JS文件。如果CPU密集型代码位于对象的方法中该怎么办?为每个CPU密集型方法编写JS文件真是太糟糕了。

另一个建议是生成子进程,但这会使代码更难维护。

有任何克服这种(感知的)障碍的建议吗?如何使用Node.js编写干净的面向对象代码,同时确保CPU重任务执行异步?

5 个答案:

答案 0 :(得分:277)

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

编辑:我想出了一个类比;网络应用程序应该作为一个餐厅。你有服务员(网络服务员)和厨师(工人)。服务员与客户保持联系并完成简单的任务,例如提供菜单或解释某些菜是素食的。另一方面,他们将更艰巨的任务委托给厨房。因为服务员只做简单的事情,他们反应迅速,厨师可以集中精力完成工作。

这里的Node.js将是一个单一但非常有才华的服务员,可以一次处理多个请求,而Apache将是一群愚蠢的服务员,每个服务器只处理一个请求。如果这个Node.js服务员开始做饭,那将是一场直接的灾难。尽管如此,烹饪也可能耗尽大量的阿帕奇服务员,没有提到厨房里的混乱和响应能力的逐步降低。

答案 1 :(得分:50)

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

像这样https://github.com/resque/resque

以下是github上有关他们为什么要构建它的文章http://github.com/blog/542-introducing-resque

答案 2 :(得分:12)

您不希望CPU密集型代码执行异步,您希望它并行执行 。您需要从提供HTTP请求的线程中获取处理工作。这是解决这个问题的唯一方法。使用NodeJS,答案是cluster module,用于生成子进程来完成繁重的工作。 (AFAIK节点没有任何线程/共享内存的概念;它是进程或什么都没有)。您有两种选择来构建应用程序。您可以通过生成8个HTTP服务器并在子进程上同步处理计算密集型任务来获得80/20解决方案。这样做很简单。你可以花一个小时在那个链接上阅读它。事实上,如果您只是删掉该链接顶部的示例代码,那么您将获得95%的支持。

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

我很惊讶,即使提及群集也没有其他答案。

背景: 异步代码是暂停的代码,直到某些事情发生其他地方,此时代码唤醒并继续执行。一个非常常见的情况是I / O必须在其他地方发生缓慢的事情。

如果您的处理器负责执行工作,则异步代码无用。这正是“计算密集型”任务的情况。

现在,看起来异步代码似乎是利基,但事实上它很常见。它恰好对计算密集型任务没用。

等待I / O是一种总是在Web服务器中出现的模式。连接到服务器的每个客户端都会获得一个套接字。大多数情况下插座都是空的。在套接字收到某些数据之前,您不想做任何事情,此时您要处理请求。在引擎盖下,像Node这样的HTTP服务器正在使用事件库(libev)来跟踪数千个打开的套接字。操作系统通知libev,然后当其中一个套接字获取数据时,libev会通知NodeJS,然后NodeJS将事件放入事件队列,此时您的http代码就会启动并一个接一个地处理事件。在套接字有一些数据之前,事件不会被放入队列,因此事件永远不会等待数据 - 它已经存在于它们中。

单线程基于事件的Web服务器作为一种范例是有意义的,当瓶颈在一堆大多数空插槽连接上等待并且您不希望每个空闲连接都有一个完整的线程或进程而您不希望轮询你的250k套接字,找到下一个有数据的套接字。

答案 3 :(得分:7)

您可以使用多种方法。

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

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

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

答案 4 :(得分:0)

使用child_process是一种解决方案。但是与Go goroutines

相比,产生的每个子进程可能会占用大量内存。

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