在 NestJS HTTP 服务器中使用子进程时,受 CPU 限制的进程会阻塞工作池

Posted

技术标签:

【中文标题】在 NestJS HTTP 服务器中使用子进程时,受 CPU 限制的进程会阻塞工作池【英文标题】:CPU-bound process blocks worker pool while using Child Process in NestJS HTTP server 【发布时间】:2019-04-23 06:50:30 【问题描述】:

节点版本: v10.13.0

我正在尝试对涉及大量 CPU 计算的 NodeJS 请求并发进行非常简单的测试。我知道 NodeJS 不是 CPU 密集型进程的最佳工具,不应该系统地生成子进程,但这段代码是为了测试子进程的工作方式。这也是用 TypeScript 编写的,使用 NestJS。

src/app.controller.ts

import  Get, Param, Controller  from '@nestjs/common';
import fork = require('child_process');

@Controller()
export class AppController 
  @Get()
  async root(): Promise<string> 
    let promise = new Promise<string>(
        (resolve, reject) => 
          // spawn new child process
          const process = fork.fork('./src/cpu-intensive.ts');
          process.on('message', (message) => 
            // when process finished, resolve
            resolve( message.result);
          );
          process.send();    
        
    );    
    return await promise;
  

src/cpu-intensive.ts

process.on('message', async (message) => 
  // simulates a 10s-long process
  let now = new Date().getTime();
  let waittime = 10000; // 10 seconds
  while (new Date().getTime() < now + waittime)  /* do nothing */ ;
  // send response to master process
  process.send( result: 'Process ended' );
);

这么长的过程,如果在没有产生新子进程的情况下执行,会导致这个结果时间线,有 5 个并发请求(从 #1 到 #5 注明)。每个进程阻塞循环事件,每个请求都必须等待前一个请求完成才能得到响应。

Time 0    10   20   30   40   50
#1   +----+
#2   +----+----+
#3   +----+----+----+
#4   +----+----+----+----+
#5   +----+----+----+----+----+

在生成新的子进程时,我期望每个进程将由我的 CPU 上的不同逻辑核心同时处理(我的有 8 个逻辑核心),导致这个预测的时间表:

Time 0    10   20   30   40   50
#1   +----+
#2   +----+
#3   +----+
#4   +----+
#5   +----+

虽然,我在每次测试中都观察到了这个奇怪的结果

Time 0    10   20   30   40   50
#1   +----+
#2   +----+----+
#3   +----+----+----+
#4   +----+----+----++
#5   +----+----+----+-+

前 3 个请求就像工作池被饿死一样,尽管我假设会创建 3 个不同的池。最后的 2 个请求非常令人困惑,因为它们的行为就像与请求 #3 并行工作。

我目前正在寻找以下原因的解释:

为什么前 3 个请求不像同时运行一样 为什么最后 3 个请求表现得好像同时运行

请注意,如果我添加另一个“快速”方法如下:

  @Get('fast')
  async fast(): Promise<string> 
    return 'Fast process ended.';
  

这种方法不受并发运行的 CPU 密集型进程的影响,并且总是立即回复。

【问题讨论】:

有任何更新吗? 您是否每次都能始终如一地获得这些结果? 据我了解,这是因为当我们调用子进程并开始执行 cpu 密集型工作时,所以当时我们的主线程正在执行同步操作,所以它无法处理来自的响应子进程,当主线程完成其工作之后,它能够处理来自子进程的响应。所有操作都在并行执行,但由于同步操作,主线程无法处理响应。继续它。我希望它有意义。对于 POC,您可以在子进程中执行一些操作 n 检查操作是否在开始时间完成。 更多细节可以阅读这个节点问题github.com/nodejs/node/issues/14917 @mihai:是的,这是一致的 【参考方案1】:

我在我的机器上执行了测试用例,它工作正常,你能在你的机器上检查一下吗?

节点版本:v8.11.2 操作系统:macOs High Sierra 10.13.4, 8 Cores

child-process-test.js

const child_process = require('child_process');  
for(let i=0; i<8; i++)   
    console.log('Start Child Process:',i,(new Date()));
    let worker_process = child_process.fork("cpu-intensive-child.js", [i]);    
    worker_process.on('close', function (code)   
        console.log('End Child Process:', i , (new Date()), code);  
    );

cpu-intensive-child.js

const fs = require('fs');
// simulates a 10s-long process
let now = new Date().getTime();
let waittime = 10000; // 10 seconds
while (new Date().getTime() < now + waittime)  /* do nothing */ ;
// send response to master process
// process.send( result: 'Process ended' );

输出

您可以在输出中检查所有过程的差异仅为10 sec,您可以在您的机器上执行此测试用例并告诉我,可能会有所帮助。

【讨论】:

确实有效。然而这不是同一个用例,因为这里没有外部事件处理,对吧?感谢您的努力! 是的,这里没有外部事件处理。在您的用例中,您创建了一个http-server,否则没有区别。我认为两者应该工作相同。 他们可能应该,但他们没有,不像我那样使用 NestJS。那么http服务器层上一定有什么东西会产生我描述的奇怪行为?至少您强调不是导致它的 child_process 。我会尽量减少用例(不直接使用 NestJS 种子)。

以上是关于在 NestJS HTTP 服务器中使用子进程时,受 CPU 限制的进程会阻塞工作池的主要内容,如果未能解决你的问题,请参考以下文章

Nestjs如何同时使用http请求和Websocket

运行nestjs应用程序时typeorm迁移中的“不能在模块外使用import语句”

在 NestJS 微服务中公开普通的 http 端点

如何在 nrwl monorep 中调试 NestJS 应用程序

NestJS - 在微服务中结合 HTTP 和 RabbitMQ

Python 子进程在发出 HTTP 请求时静默崩溃