Node.js 的 HTTP 服务器在使用 cluster 的情况下也是一个个请求排队处理吗?
发布于 5 年前 作者 hxddev 3985 次浏览 来自 问答

先贴代码:

const Koa = require("koa");
const cluster = require("cluster");
const numCPUs = require("os").cpus().length;

if (cluster.isMaster) {
  console.log(`主进程 ${process.pid} 正在运行`);

  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }
} else {
  const app = new Koa();

  app.use(async ctx => {
    const time = parseInt(new Date().getTime() / 1000);
    await new Promise(resolve => setTimeout(resolve, 10 * 1000));
    ctx.body = `[${process.pid}] time: ${time}`;
  });

  app.listen(3000);

  console.log(`工作进程 ${process.pid} 已启动`);
}

await new Promise(resolve => setTimeout(resolve, 10 * 1000)); 阻塞请求,模拟耗时操作(调用第三方接口)。 手工测试:浏览器开三个标签页,看 进程号不同 并且 时间戳刚刚好都是差10秒。 ab 测试:ab -n 30 -c 10 http://127.0.0.1:3000/ 在测试修改进程数量对结果没有影响。

求大神解答。

14 回复

如果真的是这样,是不是只能多个 Docker 容器或者启动多个 Node.js 应用然后用 Nginx 之类的均衡负载?

看不懂你这个测试的作用是什么。 有关异步的理解,请参考: https://juejin.im/post/5ab8ff776fb9a028e46ea3e2

首先说明一点: 单线程没有线程切换和通信的损耗!一般情况下不建议使用多线程。 libuv 是有个队列,但是并不阻塞。实际你可以做数据库查询,压测。 我们之前压测结果: 单核 1G 内存, 2000多RPS (数据库在另外一台服务器)

@zuohuadong 主要是测试模拟高耗时接口(调用第三方支付接口等,耗时在 1s 到 1.5s 左右),测试代码故意模拟写成 10 秒。

以测试为例子,发起第一个请求需要 10 秒才能响应。 第二个请求如果在第一个请求还没有响应时就发起了那它响应时间就是为 “第一个请求剩余耗时 + 第二个请求的耗时”。 第三个,第四个请求也是以此类推。那如果假设同时发起 10 个请求,那么其中就会有一个请求会是 100 秒才响应!

于是,我想 cluster 是不是能帮我解决这个问题。几个线程就是能同时处理多少个请求。 cluster 给我的感觉就是想 Nginx 负载均衡一样(开多个 Node.js 应用实例,每个实例不同端口,然后 Nginx 配置把流量转发每个实例)。

可是经过实验我发现我好像想错了。 希望有大神能帮我解答下,有没有只在 Node.js 里面就能解决这个问题的。

lz提问题没抓住重点。你到底是什么需求?

并不是排队处理的,cluster模块有一个简单的调度轮询机制,会将http请求分配给可用、处于等待中的进程。能做到某种程度上的负载均衡(做不到粘性负载均衡)。

@hxddev 你这个是有问题的。 单线程异步的情况下,由于主线程会把它扔到队列,这个耗时在ns 级别。 所以正常情况下,如果同时发起请求,第一个请求是 10s+3ns,第二个是 10s+ 6ns ,第三个是 10s + 9ns 整个过程可能不到 10s + 1ms 。

附上代码: [npm i fastify]

const fastify = require('fastify')();
const util = require('util');
const setTimeoutPromise = util.promisify(setTimeout);

let num = 0
fastify.get('/', (request, reply) => {
  // return (Date.now());
  setTimeoutPromise(10000, 'foobar').then((value) => {
    num++;
    console.log(num);
    let time = Date.now()
    console.log(time);
    //return { time };
    // value === 'foobar' (传值是可选的)
    // 在大约 10000 毫秒后执行。
    reply.send(time);
  });

})

const start = async () => {
  try {
    await fastify.listen(3000)
  } catch (err) {
    fastify.log.error(err);
    process.exit(1);
  }
}
start();

测试结果: image.png

貌似master里需要自己实现调度逻辑

@zuohuadong 感谢,真相大白了。 原来的测试例子没有问题,是测试方法有问题。

用 Chrome 的标签页测试就会这样。 CURL 没有出现这种情况,可能是 Chrome 的某种机制,现在在查 Chrome 相关资料。

@hxddev 另外,没必要过早使用多线程,只会增加复杂度。 只在CPU 密集型应用中考虑多线程。

这个问题是因为如果用chrome不同的tab打开同一个url的时候,每个tab会等待前面一个tab执行完才会执行

这个官方文档有写呀,默认 Round-robin:cluster how it works

The first one (and the default one on all platforms except Windows), is the round-robin approach, where the master process listens on a port, accepts new connections and distributes them across the workers in a round-robin fashion, with some built-in smarts to avoid overloading a worker process.

回到顶部