【问题标题】:Can I yield to a child process and return the response in Node.js?我可以屈服于子进程并在 Node.js 中返回响应吗?
【发布时间】:2017-09-24 21:47:46
【问题描述】:

简而言之,我遇到了一个问题,即对我的 Node.js 服务器的多个并行 GET 请求导致服务器“阻塞”并挂起,从而导致客户端超时(503,服务不可用)。

经过大量性能分析后,我意识到这是 CPU 问题。具体请求(我们称之为GET /foo)通过HTTP从多个服务中查询数据,然后进行大量计算,并将结果返回给客户端,如下所示:

  1. 客户端请求GET /foo
  2. /foo 控制器通过 HTTP 从多个其他服务查询数据`
  3. /foo 控制器然后对数据进行一系列迭代,为客户端编译一些输出

第 3 步大约需要 2 秒才能完成。但是,如果我向/foo 并行发送 2 个请求,每个客户端将在大约 4 秒内收到他们的响应。当我在使用更多内核的集群中运行应用程序时,请求运行得更快,但不是我想要的。

似乎我在这里有几个选择:

  1. 预先计算响应(最好暂时避免这种情况,因为它需要一个完整的“缓存失效”方案),或者
  2. /foo 将 CPU 阻塞计算异步发送到另一个进程(使用 Heroku,所以这将是另一个 dyno),然后我可以使用 websocket 或其他东西将结果推送到客户端(再次,对于我的情况非常复杂) , 或
  3. 以某种方式让出请求中的子进程并将结果返回给客户端

愿意做类似于选项 3 的事情。这样的事情:

get('/foo', function*(request) {
  // I/O, so not blocking the event loop (I think)
  let data = yield getData(request)

  // make this happen in a different process
  let response = yield doSomeHeavyProcessing(data)

  return response
})

上面我省略了很多实现细节,但如果有必要知道,我使用的是Koa和Node.js 6。

理想情况下,doSomeHeavyProcessing 会在某个单独的进程中执行 CPU 密集型计算,完成后,仍以“同步”方式将结果发送回请求客户端。

我一直试图围绕子进程、网络工作者、纤维等进行思考,并且一直在用这些做一些基本的“hello world”,以使它们基本上完成上述操作,但无济于事。如有需要,可以发布更多详细信息。

【问题讨论】:

    标签: javascript node.js heroku koa


    【解决方案1】:

    您可以尝试以下方法:

    1. 将阻塞计算分成小块并使用setImmediate 将下一个工作块放在事件队列的末尾。因此计算不再阻塞,可以处理其他请求。

    2. 微软最近发布了napajs。正如他们的自述文件中所述

    随着 Node.js 的发展,我们发现它可以在 CPU 密集型任务中补充 Node.js,它能够在多个 V8 隔离中执行 JavaScript 并在它们之间进行通信。

    我没试过,但看起来很有希望:

    var napa = require('napajs');
    var zone1 = napa.zone.create('zone1', { workers: 4 });
    
    get('/foo', function*(request) {
      let data = yield getData(request)
    
      let response = yield zone1.execute(doSomeHeavyProcessing, [data])
    
      return response
    })
    

    3. 如果以上都不够,并且您需要将负载分散到多台机器上,那么您可能无法避免使用某种消息队列将工作分配到不同的服务器。在这种情况下,请查看ZeroMQ。它非常易于从节点使用,您可以使用它实现任何类型的分布式消息传递模式。

    【讨论】:

      【解决方案2】:

      为方便起见,您可以使用带有附加包装器的 Child process

      worker.js - 此模块将在单独的进程中运行,并会完成繁重的工作

      const crypto = require('crypto');
      
      function doHeavyWork(data) {
        return crypto.pbkdf2Sync(data, 'salt', 100000, 64, 'sha512');
      }
      
      process.on('message', (message) => {
        const result = doHeavyWork(message.data);
        process.send({ id: message.id, result });
      });
      

      client.js - 子进程

      的方便(但原始)包装器
      const cp = require('child_process');
      
      let worker;
      const resolves = new Map();
      
      module.exports = {
        init(moduleName, errorCallback) {
          worker = cp.fork(moduleName);
          worker.on('error', errorCallback);
      
          worker.on('message', (message) => {
            const resolve = resolves.get(message.id);
            resolves.delete(message.id);
            if (!resolve) {
              errorCallback(new Error(`Got response from worker with unknown id: ${message.id}`));
              return;
            }
      
            resolve(message.result);
          });
      
          console.log(`Service PID: ${process.pid}, Worker PID: ${worker.pid}`);
        },
      
        doHeavyWorkRemotly(data) {
          const id = `${Date.now()}${Math.random()}`;
      
          return new Promise((resolve) => {
            worker.send({ id, data });
            resolves.set(id, resolve);
          });
        }
      }
      

      我使用fork() 来利用文档中所述的额外通信渠道。

      我还记录所有提交给工作进程的请求 (const resolves = new Map();) 并仅在工作进程返回特定请求 (const resolve = resolves.get(message.id);) 的响应时才解析 Promise (resolve(message.result);)。

      run.js - 一个启动模块,它利用co 来“执行”生成器。

      const co = require('co');
      
      const client = require('./client');
      
      function errorCallback(error) {
        console.log('Got an unexpected error!');
        console.log(error);
      }
      
      client.init('./worker.js', errorCallback);
      
      function* run() {
        while(true) {
          yield client.doHeavyWorkRemotly('mydata');
        }
      }
      
      co(run);
      

      要测试它,只需运行node run.js,它将打印

      服务 PID:XXXX,工作人员 PID:XXXX

      然后看一下 CPU 利用率,worker 进程可能会占用大约 100% 的 CPU,而 Service 会非常空闲。

      【讨论】:

        猜你喜欢
        • 2012-11-10
        • 2015-06-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-05-05
        • 1970-01-01
        • 2015-12-28
        • 2019-12-20
        相关资源
        最近更新 更多