【问题标题】:Blocking requests not running simultaneously on PM2阻塞请求不在 PM2 上同时运行
【发布时间】:2021-04-04 21:53:55
【问题描述】:

在我的 express 应用程序中,我在我的应用程序中定义了 2 个端点。一种用于 is-sever-up 检查,一种用于模拟阻塞操作。

app.use('/status', (req, res) => {
    res.sendStatus(200);
});

app.use('/p', (req, res) => {
    const { logger } = req;
    logger.info({ message: 'Start' });
    let i = 0;
    const max = 10 ** 10;
    while (i < max) {
        i += 1;
    }
    res.send(`${i}`);
    logger.info({ message: 'End' });
});

我使用 winston 进行日志记录,使用 PM2 进行集群,使用以下命令

$ pm2 start bin/httpServer.js -i 0

它已经启动了 4 个实例。

现在,当我在不同的选项卡中访问/p/p/status 按顺序路线时,(请求 1 和请求 2)和(请求 2 和请求 3),我预计会在一段时间后收到请求 1 和请求 2 的响应,但延迟大约 1 秒,请求 3 的响应应该会立即响应。

实际:请求 3 的响应确实立即出现,但请求 1 和请求 2 发生了一些奇怪的事情。请求 2 甚至在请求 1 完成之前才开始。这是我得到的日志。您可以看到请求 1 结束和请求 2 开始的时间戳。

{"message":"Start","requestId":"5c1f85bd-94d9-4333-8a87-30f3b3885d9c","level":"info","timestamp":"2020-12-28 07:34:48"}
{"message":"End","requestId":"5c1f85bd-94d9-4333-8a87-30f3b3885d9c","level":"info","timestamp":"2020-12-28 07:35:03"}
{"message":"Start","requestId":"f1f86f68-1ddf-47b1-ae62-f75c7aa7a58d","level":"info","timestamp":"2020-12-28 07:35:03"}
{"message":"End","requestId":"f1f86f68-1ddf-47b1-ae62-f75c7aa7a58d","level":"info","timestamp":"2020-12-28 07:35:17"}

为什么请求 1 和请求 2 没有同时启动(当然有 1 秒的延迟)?如果它们是同步运行的,为什么请求 3 会立即响应,而不是等待请求 1 和请求 2 完成?

【问题讨论】:

  • 是的。 Node.js 是单线程的。因此,执行长时间运行的代码(如 long while 或 for 循环)将导致整个服务器无响应。然而,像 Netflix 和 Twitter 这样的人使用 Node.js 的原因是,Web 服务器很少会运行长时间运行的阻塞任务。相反,99% 的 Web 服务器所做的工作是等待来自其他服务或数据库的响应。等待可以在单线程软件中使用各种异步 I/O API 并行完成。执行代码不能并行完成。
  • 但是在这里,我有 4 个实例。所以,我想知道为什么请求 2 没有被另一个实例启动或接收?由于 PM2,所有这些实例都并行运行,对吗?

标签: javascript node.js express pm2 winston


【解决方案1】:

这是因为在您的节点服务器默认响应的响应中,标头的连接是keep-alive。因此,当您使用浏览器时,连接将被重用(curl 也可以模拟重用连接的情况)。这意味着多个请求由同一实例在指定时间内提供服务。即使您有多个节点实例。

注意:您可以在响应标头中看到指定时间,例如 Keep-Alive: timeout=5 如果您使用浏览器,请打开网络选项卡以查看响应标头。
如果使用 curl,请添加 -v 选项以查看响应标头

您可以尝试在终端中同时使用多个分隔的curl 命令分隔的curl 命令表示连接不会被重复使用。因此,您将获得预期的结果。您可以在/status 路由器中添加console.log("status test")。然后,使用pm2 logs 来查看哪个实例服务于请求,如下格式(这些日志是通过使用浏览器访问端点产生的)。

0|server  | status test
0|server  | status test

0 表示第一个实例,当您使用浏览器访问端点时,您会看到这是所有相同的实例来服务请求。但是,如果你使用curl,你会发现这个数字总是改变的,这意味着每个请求都由不同的节点实例提供服务。

您可以看到我在终端中使用 curl 同时发送了两个请求。然后,不同的节点实例来服务请求。所以,console.log 的开始和结束时间是相同的。在这个例子中,我有 8 个事件循环,所以我可以同时处理 8 个长处理(同步代码)请求。

而且,您可以使用curl 来模拟keep-alive 的情况。然后,您会看到请求由同一个节点实例提供服务。

curl http://localhost:8080/status http://localhost:8080/status -v -H "Connection: keep-alive"

您也可以使用connection close 来查看请求是由不同的节点实例服务的。

curl http://localhost:8080/status http://localhost:8080/status -v -H "Connection: close"

你可以在这里看到不同。

如果要关闭服务器端的连接,可以使用以下代码。

res.setHeader("Connection", "close")

这是我的测试代码。

const express = require("express")
const app = express();
const port = 8080;

app.use('/status', (req, res) => {
    console.log("status tests");
    res.sendStatus(200);
});

app.use('/p', (req, res) => {
    console.log(new Date() + " start");
    let i = 0;
    const max = 10 ** 10;
    while (i < max) {
        i += 1;
    }
    res.send(`${i}`);
    console.log(new Date() + " end");
});

app.listen(port, () => {
    return console.log(`server is listening on ${port}`);
});

【讨论】:

  • 这完全有道理。所以任何想法,我如何在浏览器中解决这个问题?在我的请求中添加 "Connection: close" 标头会修复它吗?
  • 是的,您可以使用res.setHeader("Connection", "close") 关闭连接。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-01-21
  • 1970-01-01
  • 2011-07-07
  • 2013-11-12
  • 1970-01-01
  • 2016-05-07
  • 1970-01-01
相关资源
最近更新 更多