如何使用Node.js 集群(cluster)扩展你的 Node.js 应用

639 阅读9分钟

如何在多个处理器核心上扩展你的NodeJS应用执行

当涉及到商业应用程序时,我们经常努力提高性能。但是,有时候,我们会在潜在的权衡中前进。

NodeJS就是这样的一个案例。由于它使用JavaScript,我们经常毫不犹豫地选择它。但是,NodeJS有一些固有的限制,会影响一些工作负载的性能。

因此,在这篇文章中,让我们看看这样一个用例,优化NodeJS应用程序来处理繁重的工作负载。

什么是NodeJS集群?

没有多少人意识到,NodeJS默认在单线程中运行。此外,它只为这个线程利用了一个CPU核心。因此,举例来说,如果你使用的是一台4核机器,Node将只使用一个核心。

为了利用多核系统的优势,我们需要启动一个NodeJS进程的集群来处理负载。

因此,我们可以使用NodeJS集群 模块来利用系统中所有的处理器。它将旋转你的程序的副本,所以如果你在一个4核机器中,你将运行你的程序的4个副本。这意味着你可以处理4倍的流量容量。这不可避免地给你的NodeJS应用程序带来了性能提升。

集群模块是如何工作的?

NodeJS Cluster模块创建了几个子进程(worker),它们平行运行,共享同一个服务器端口。

每个生成的子进程都有自己的V8实例、事件循环和内存。主进程和子进程之间通过IPC(进程间通信)进行通信。

有了多个进程,如果一个进程被占用了CPU密集型任务,其他进程可以利用其他可用的CPU/cores处理其他请求。集群模块的有效性在于工作者平衡负载,应用程序不会因为重负载而停顿。

传入的负载以下列任何一种方式在子进程之间进行分配。

  • 父进程在一个端口上监听传入的负载,并在工作者之间以轮流的方式分享它们。除了Windows之外,这是所有系统的默认模式。
  • 父进程产生一个监听套接字,并将其分配给感兴趣的工作者,然后他们可以立即处理到来的流量。

优点。

  • 使用所有可用的内核来执行应用程序,提高了可扩展性。
  • 如果一个生成的进程意外或故意死亡,可以立即创建一个新的进程来替代死亡的进程,而无需等待或任何人工干预。
  • 它的设置很简单,因为NodeJS模块处理一切,没有必要添加额外的依赖性。
  • 通过利用处理器的全部容量,浪费的资源数量大幅减少。

缺点是。

  • 会话管理是不可能的;相反,开发人员必须处理替代方案,这增加了复杂性。
  • IPC对于管理一个应用程序来说是一个耗时的任务,对于一些应用程序来说,不建议使用。

如何在你的Node应用中使用集群?

为了理解集群的好处,我们将看一个有集群的NodeJS应用样本与没有集群的NodeJS应用进行比较。

让我们开始创建一个没有集群的简单NodeJS服务器,执行一个CPU密集型的任务,故意阻塞事件循环。

设置一个简单的NodeJS Express服务器

通过在终端上执行以下命令建立一个新项目。

mkdir nodejs-clustering

然后创建一个名为 "no-clustering.js "的文件,之后,你的项目文件夹结构应该是这样的。

作者的屏幕截图

在no-clustering.js文件中加入这段代码。

medium.com/media/13b1e…

让我们检查一下这段代码的作用。我们将从一个运行在3000端口的基本Express服务器开始。它有两个(/)URI,显示一个信息 "Hi There!这个应用程序不使用集群......",而/api/nocluster是一个替代路线。

nocluster API GET方法中的冗长循环循环了8⁷(2,097,152)次。它在每个循环中都执行math.pow(),即返回基数到指数幂,并将其添加到一个数字中,并分配给结果变量。然后,它在控制台记录这个数字并将其作为响应返回。

它被设计成阻塞和CPU密集型,以便观察它之后会对集群产生什么影响。

用node no-clustering.js运行服务器,在进入http://localhost:3000/api/nocluster,你将能看到以下输出。

作者的屏幕截图

运行Node服务器的终端会出现这样的情况。

作者的屏幕截图

在NodeJS Express服务器上添加集群功能

添加一个with-clustering.js文件,内容与no-clustering.js文件相同,但这里我们将使用Cluster模块。

medium.com/media/81e48…

如果我们从一开始就剖析这段代码,你可以看到我们已经要求使用Cluster模块,并且require('os').cpus().length返回可用的CPU核数,在我的机器上是8。首先,我们检查集群是否是一个主进程;然后我们根据可用的核心数分叉出类似数量的子进程(工作者)。只有当它不是一个主进程时,工人才会调用启动函数。

该程序的工作原理与前面类似,但这次我们要创建许多子进程,它们将共享3001端口,并能够处理向该端口发出的请求。fork()技术被用来启动工作进程。它返回一个ChildProcess对象,该对象有一个集成的数据传输连接,用于在子进程和其父进程之间传递消息。

当你在with-clustering.js文件中运行上述代码时,它将在http://localhost:3001/api/withcluster,给出以下输出。

作者的屏幕截图

终端将出现如下图所示。你可以看到所有可用的8个核心都在这里得到了利用。

作者的屏幕截图

然而,最好的做法之一是,你不应该创建比可用逻辑核心数量更多的工作者,从而导致调度开销。

这是因为系统必须安排所有新形成的进程,所以每个进程都要轮流使用少数可用的核心。

用Bit构建和共享独立的JS组件

比特是一个可扩展的工具,可以让你用_独立_编写、版本和维护的组件创建_真正的_模块化应用 。

用它来构建模块化的应用程序和设计系统,编写和交付微前端和微服务,或者只是在应用程序之间共享组件。

一个独立的源代码控制和共享的 "卡片 "组件。右边是=>它的依赖关系图,由Bit自动生成。

Bit: 模块化网络的平台

性能比较

为了说明集群对NodeJS应用程序性能的影响,让我们通过对这两个应用程序进行负载测试来测试它们如何管理大量的传入连接。这将用loadtest包进行。

loadtest包可以让你很容易地通过模拟大量的并行连接来创建测试你的API的性能。

首先,你需要用下面的命令在全局范围内安装loadtest包。

npm install -g loadtest

然后运行你需要进行负载测试的应用程序。当它运行时,打开另一个终端,执行下面的命令来运行负载测试。

我将首先测试 "无集群"的应用程序。

loadtest http://localhost:3000/api/nocluster -n 1000 -c 100

这将设置最大请求数为1000,应用程序URL的并发水平为100。在测试结束后,你会得到以下结果。

服务器能够每秒处理7个请求,平均延迟为13168毫秒(完成一个请求的平均时间)。

然后停止运行 "无集群"的应用程序,并测试我们开发的 "有集群"的应用程序,进行同样的负载测试。

loadtest http://localhost:3001/api/withcluster -n 1000 -c 100

下面是有集群的应用程序的测试结果。

与没有集群的应用程序每秒处理7个请求相比,有集群的应用程序能够每秒处理35个请求

有集群的应用程序的平均延迟为2745.6 毫秒,而没有集群的应用程序的延迟为13168毫秒。

很明显,集群对该应用程序做了一个重大的改进。它的性能比以前提高了5倍

总结

上述结果不言自明。在单线程环境下,每个请求的时间随着并发请求数量的增加而急剧增加。当应用程序升级到利用集群时,我们见证了吞吐量的大幅提升。

你需要记住的另一个重要事实是 "集群擅长于CPU密集型任务"。因此,当你的应用程序被期望执行这样的操作时,集群提供了一个关于它可以同时执行的操作数量的好处。但是,如果你的软件不执行许多CPU密集型操作,由于资源分配过多,通常不建议启动大量的子NodeJS进程。

所以,我希望这篇文章能让你深入了解使用集群模块来扩展你的NodeJS应用程序。然后,在你的下一个Node项目中尝试这样做,以充分利用所有可用的CPU核心。它将允许你通过更好地利用系统资源来提高你的NodeJS应用程序的性能。

感谢阅读.......!!!

了解更多


NodeJS的集群性能优化最初发表在Medium上的Bits and Pieces中,人们通过强调和回应这个故事来继续对话。