232

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

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

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

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

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

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

4

5 回答 5

303

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

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

这里的 Node.js 将是一个单一但非常有才华的服务员,可以一次处理许多请求,而 Apache 将是一群愚蠢的服务员,每个人只处理一个请求。如果这个 Node.js 服务员开始做饭,那将是一场直接的灾难。尽管如此,即使是大量的 Apache 服务员,烹饪也可能会耗尽,更不用说厨房里的混乱和反应能力的逐渐下降了。

于 2010-08-16T09:25:02.573 回答
60

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

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

这是来自 github 的一篇关于他们为什么构建它的文章http://github.com/blog/542-introducing-resque

于 2010-08-21T03:39:55.937 回答
24

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

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

我很惊讶这些其他答案都没有提到集群。

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

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

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

例如,等待 I/O 是一种经常发生在 Web 服务器中的模式。每个连接到您的服务器的客户端都会获得一个套接字。大多数时候套接字是空的。在套接字接收到一些数据之前,您不想做任何事情,此时您想要处理请求。在底层,像 Node 这样的 HTTP 服务器正在使用事件库 (libev) 来跟踪数千个打开的套接字。操作系统会通知 libev,然后当其中一个套接字获取数据时,libev 会通知 NodeJS,然后 NodeJS 会在事件队列中放置一个事件,此时您的 http 代码会启动并一个接一个地处理这些事件。在套接字有一些数据之前,事件不会被放入队列,因此事件永远不会等待数据 - 它已经为它们准备好了。

单线程基于事件的 web 服务器作为一种范式是有意义的,当瓶颈在等待一堆大部分为空的套接字连接时,您不希望每个空闲连接都有一个完整的线程或进程,并且您不想轮询 250k sockets 来找到下一个有数据的。

于 2017-02-21T05:21:32.967 回答
7

您可以使用几种方法。

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

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

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

于 2010-08-23T04:29:28.667 回答
0

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

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

于 2018-06-20T10:27:21.260 回答