【问题标题】:Multithread request handling in node.js (deployed in Kubernetes and behind Nginx)node.js 中的多线程请求处理(部署在 Kubernetes 和 Nginx 后面)
【发布时间】:2021-01-26 01:25:45
【问题描述】:

我有一个单线程、基于 Web 的 CPU 密集型工作负载,以 Node.js 服务器(express)的形式实现,并部署在 Kubernetes 上,没有 CPU 请求/限制(尽力而为)。在四核物理机上执行此工作负载平均需要约 700-800 毫秒。服务器位于 Nginx 负载均衡器后面(均采用默认配置)。简而言之,工作量简单如下:

for (let $i = 0; $i < 100; $i++) {
    const prime_length = 100;
    const diffHell = crypto.createDiffieHellman(prime_length);
    const key = diffHell.generateKeys('base64');
    checksum(key);
}

我的 Express app.js 中有一个事件处理程序,它在接收或发送请求时在控制台中记录时间戳,如下所示:

app.use((req, res, next) => {
    const start = process.hrtime();
    console.log(`Received ${req.method} ${req.originalUrl} from ${req.headers['referer']} at {${now()}} [RECEIVED]`)

    res.on('close', () => {
        const durationInMilliseconds = helper.getDurationInMilliseconds(start);
        console.log(`Closed received ${req.method} ${req.originalUrl} from ${req.headers['referer']} {${now()}} [CLOSED] ${durationInMilliseconds.toLocaleString()} ms`)
    });
    next();
})

我同时从 3 台不同的物理机向该服务发送 3 个并行请求。所有这些服务器以及所有 kubernetes 节点都启用了 NTP,并且它们的本地时间同步在一起。

为了运行流量,我在单独的screen(使用linux的屏幕)中ssh到所有这3台服务器,在命令行中准备curl命令,然后使用以下命令发送并输入以运行流量同时:

screen -S 12818.3 -X stuff "
" & screen -S 12783.2 -X stuff "
" & screen -S 12713.1 -X stuff "
"

从日志中,我可以看到所有 3 个请求同时发送:在 17:26:37.888

有趣的是,服务器在完成下一个请求后立即收到每个请求:

  • 请求 1 在 17:26:37.922040382 收到,需要 740.128 毫秒 过程
  • 请求 2 在 17:26:38.663390107 收到,需要 724.524ms 过程
  • 请求 3 在 17:26:39.388508923 收到,需要 695.894 毫秒 过程

这是容器中生成的日志(使用kubectl logs -l name=s1 --tail=9999999999 --timestamps提取):

2020-10-11T17:26:37.922040382Z Received GET /s1/cpu/100 from undefined at {1602429997921393902} [RECEIVED]
2020-10-11T17:26:38.662193765Z Closed received GET /s1/cpu/100 from undefined {1602429998661523611} [CLOSED] 740.128 ms
2020-10-11T17:26:38.663390107Z Received GET /s1/cpu/100 from undefined at {1602429998662810195} [RECEIVED]
2020-10-11T17:26:39.387987847Z Closed received GET /s1/cpu/100 from undefined {1602429999387339320} [CLOSED] 724.524 ms
2020-10-11T17:26:39.388508923Z Received GET /s1/cpu/100 from undefined at {1602429999387912718} [RECEIVED]
2020-10-11T17:26:40.084479697Z Closed received GET /s1/cpu/100 from undefined {1602430000083806321} [CLOSED] 695.894 ms

我使用htoppidstat 检查了CPU 使用率,奇怪的是一直只使用1 个内核...

我期待 node.js 服务器同时接收所有请求并在不同的线程中处理它们(通过生成新线程),但似乎并非如此。如何让 node.js 并行处理请求,并利用它拥有的所有内核?

这是我的完整代码:

var express = require('express');
const crypto = require('crypto');
const now = require('nano-time');

var app = express();
function checksum(str, algorithm, encoding) {
  return crypto
      .createHash(algorithm || 'md5')
      .update(str, 'utf8')
      .digest(encoding || 'hex');
}

function getDurationInMilliseconds (start) {
  const NS_PER_SEC = 1e9;
  const NS_TO_MS = 1e6;
  const diff = process.hrtime(start);

  return (diff[0] * NS_PER_SEC + diff[1]) / NS_TO_MS;
}

app.use((req, res, next) => {
  const start = process.hrtime();
  console.log(`Received ${req.method} ${req.originalUrl} from ${req.headers['referer']} at {${now()}} [RECEIVED]`)

  res.on('close', () => {
    const durationInMilliseconds = getDurationInMilliseconds(start);
    console.log(`Closed received ${req.method} ${req.originalUrl} from ${req.headers['referer']} {${now()}} [CLOSED] ${durationInMilliseconds.toLocaleString()} ms`)
  });
  next();
})

app.all('*/cpu', (req, res) => {
  for (let $i = 0; $i < 100; $i++) {
    const prime_length = 100;
    const diffHell = crypto.createDiffieHellman(prime_length);
    const key = diffHell.generateKeys('base64');
    checksum(key);
  }
  res.send("Executed 100 Diffie-Hellman checksums in 1 thread(s)!");
});

module.exports = app;

app.listen(9121)

【问题讨论】:

    标签: node.js multithreading express nginx kubernetes


    【解决方案1】:

    我期待 node.js 服务器同时接收所有请求并在不同的线程中处理它们(通过生成新线程),但似乎并非如此。如何让 node.js 并行处理请求,并利用它拥有的所有内核?

    根据设计,node.js 仅在单个线程中运行您的 Javascript。我将其他线程用于某些事情,例如具有异步调用接口或磁盘 I/O 的内置加密操作,但是您用纯 Javascript 编写的任何内容都只能在单个线程中运行。对于 CPU 密集型 Javascript 代码,您需要专门更改代码设计,以便使用多个 CPU 来处理该 CPU 密集型代码。

    您的选择是使用child processesWorkerThreads。您可能想要做的是设置一组工作线程(可能每个 CPU 内核一个),然后为应该由工作线程处理的作业创建一个队列。然后,当作业被插入队列时,您会查看是否有可用的工作线程,如果有,您会立即将作业发送到工作线程。如果没有,则等待工作线程通知您它已完成并可用于下一个作业。

    在 node.js 中,WorkerThreads 是完全独立的 V8 Javascript 引擎实例,您可以在工作线程和主进程之间使用消息。

    【讨论】:

    • 感谢您的出色回答。我不想说你的建议不好,恰恰相反,在主线程中管理工作线程是非常合乎逻辑的。但是,这听起来有点 hacky,也是一个复杂的过程,尤其是在负载平衡工作线程之间的流量时……有没有直接的方法来管理这些线程?我以前使用过 WorkerThreads,但我发现它们使用起来并不简单......
    【解决方案2】:

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-04-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-08-08
      • 2014-11-16
      • 2010-09-18
      • 1970-01-01
      相关资源
      最近更新 更多