七爪源码:使用集群模块在 Node.js 中进行多任务处理

使用 Cluster 模块在 Node.js 中进行多任务处理的教程。

我们已经知道 Node.js 是单线程的,这意味着它将在可能具有多个内核的系统中使用处理器的单个内核。尽管它可以很好地处理单线程系统的负载,但肯定有优化的空间,集群模块是这样做的一种方式。

引入集群模块是为了通过在多个处理器内核上运行工作/子进程来扩展应用程序执行。这些进程共享相同的服务器端口,但即使使用相同的端口,这些在一天结束时也是独立的进程。所以他们每个人都有自己的 V8 实例、事件循环、自己的内存和所有这些爵士乐。这些进程使用 IPC(进程间通信)通道与父进程进行通信。在本教程中,我们将查看所有功能,所以让我们深入了解。


我们试图解决的问题

让我们通过输入 npm init -y 快速设置一个节点项目。 (为方便起见,我将添加 express,但您不一定必须这样做)。通过输入 npm i express loadtest 来安装 express 和 loadtest。我们将在本教程后面使用 loadtest 来了解使用集群模块获得的性能优势。安装后,创建一个名为 nonCluster.js 的文件并复制此代码(此文件将包含不带集群模块的代码,以进行比较)。

const app = require("express")()
const port = 3000;app.get("/heavy", (req, res) => {
   let counter = 0;
   while (counter < 900000000) {
      counter++;
   } 
   res.end(`${counter} iterations completed! 
`);
})app.get("/light", (req, res) => {
   res.send(`Done 
`);
})app.listen(port, () => console.log("Listening to port 3000"));

这是一个非常简单的快速应用程序,有 2 个请求。 /heavy 端点执行 CPU 密集型任务,阻塞事件循环。 /light 端点立即返回响应。 简单的!

现在运行服务器并首先向 /heavy 发出请求,然后向 /light 端点发出请求。 /heavy 端点显然需要时间,但您会注意到 /light 端点也需要相同的时间。 这是因为 /heavy 端点占用了计算请求的时间,因此阻塞了事件循环。 因此,在 /heavyrequest 之后发出的任何请求现在都必须等待其完成。


问题的解决方案

所以为了避免这种阻塞,让我们添加集群模块。 在名为 cluster.js 的新文件中,复制以下代码:

const cluster = require("cluster");
const port = 3000;

if (cluster.isMaster) {
    const totalCPUs = require("os").cpus().length;
    for (let i = 0; i < totalCPUs; i++) {
        cluster.fork();
    }

    cluster.on("online", (worker) => {
        console.log("Worker " + worker.process.pid + ' is online.');
    })

    cluster.on('exit', function (worker, code, signal) {
        console.log('Worker ' + worker.process.pid + ' exited with code: ' + code);
        console.log('Starting a new worker');
        cluster.fork();
    });
} else {
    const app = require('express')();

    app.get("/heavy", (req, res) => {
        let counter = 0;
        while (counter < 90000000) {
            counter++;
        }
        res.send(`${process.pid}: ${counter} iterations completed! 
`);
    })

    app.get("/light", (req, res) => {
        res.send(`${process.pid}: Done 
`);
    })
    app.listen(port, () => console.log("Listening to port 3000"));
}

让我们分解一下。 最初,当您只有一个进程时,它会解析所有进来的请求。现在我们使用集群模块,将有两种类型的进程。 一个父/主进程和一个子进程。 最初,当服务器启动时,它将启动一个进程集群。 之后,只要有人向服务器发出请求,父进程就会将请求定向到子进程(主要以循环方式)。 然后子进程将最终解决该请求。

if(cluster.isMaster){
   //spin up cluster of processes
} else{
   //resolve request
}

在 if 块中,我们将检查当前进程是否是父进程。 cluster 模块有一个名为 isMaster 的属性,它可以让您知道当前进程是子进程还是父进程。 如果它是父进程,那么我们使用 fork 方法创建集群。 (我们只启动与系统中存在的内核总数相等的进程,以避免调度开销。)如果它不是父进程,则意味着它是子进程。 所以这个子进程现在实际上负责解决请求。 它将拥有所有 API 端点及其相应的逻辑。

cluster.on("online", (worker) => {
   console.log("Worker " + worker.process.pid + ' is online.');    })     cluster.on('exit', (worker, code, signal) => {
   console.log(`${worker.process.pid} exited with code ${code});
   console.log('Starting a new worker');        
   cluster.fork();
});

在 fork 一个新的 worker 之后,它会响应一个 onlineevent。我们将在父节点上监听此事件,以查看是否所有进程都按预期创建。

当工作人员死亡时,集群模块会发出退出事件。因此,为了让我们的应用程序没有停机时间,我们将在一个进程出现故障时分叉一个新进程。这样,即使任何其他进程因任何有意或无意的原因而关闭,我们也将始终有一组进程启动并运行。

好吧,这看起来不错。现在,如果您运行此文件,并发出相同的请求(首先到 /heavyendpoint,然后到 /lightendpoint),您将看到它按预期工作而不会阻塞事件循环。因此,添加一组进程确实有帮助。现在让我们使用 loadtest 包做一些比较繁重的测试。

由于您已经运行了集群应用程序,因此我们先对其进行测试。输入 loadtest -n 1000 -c 100 http://localhost:3000/heavy 运行测试。 (如果您没有全局安装 loadtest,只需在命令开头添加 npx 即可。)

-n 代表我设置为 1000 的请求总数。-c 代表并发。它基本上将模拟一个真实世界的环境,其中应用程序同时从多个客户端获取请求,在这种情况下,它将是 100 个同时客户端。

这些是“集群”应用程序的总结结果。

//With cluster 
Total time : 7.178850510999999 s
Requests per second : 139
Mean latency : 685.8ms

现在让我们切换到“非集群”应用程序并再次运行相同的测试。

//Without cluster
Total time : 27.252680297999998 s
Requests per second : 37
Mean latency : 2583.1 ms

使用集群时,整体性能显然有显着提高。 但有一个问题。 这两个测试都是针对 /heavyendpoint 的,这是一个 CPU 密集型操作。 让我们尝试运行相同的测试,但这次是针对 /light 端点。

//With cluster
Total time: 0.5144123509999999 s
Requests per second: 1944
Mean latency: 48.8 ms//Without cluster
Total time: 0.45111675100000004 s
Requests per second: 2217
Mean latency: 42 ms

惊喜,惊喜。在此示例中使用集群时,我们实际上获得了相对较差的性能。这是为什么?

你看,Node.js 主要是为 I/O 操作而设计的,这在很大程度上不会阻塞事件循环。因此,由于它知道如何处理这些类型的操作,因此添加额外的进程并将请求路由到这些进程中的每一个最终都是一种很可能不需要的开销。但是,在 CPU 密集型阻塞操作的情况下,最好将请求数量分配给我们确实需要集群的进程。

因此,最终归结为您的应用程序的设计目的。如果您有一个微服务架构并且有一个特定的服务处理 CPU 密集型操作,您可以为该特定服务启动一个集群,其余的可以由您的单线程节点进程处理。

所以,这就是您在 Node.js 应用程序中使用集群的方式。它有自己的一套注意事项,您需要在将其添加到代码库之前了解它们。


关注七爪网,获取更多APP/小程序/网站源码资源!

发表评论
留言与评论(共有 0 条评论) “”
   
验证码:

相关文章

推荐文章