[eggjs/egg][Feature Request] 请问下eggjs如何做到多worker状态下,master平均分发请求到每个worker。

2025-10-27 398 views
0
Background

我在做一个egg流控插件,每个worker限制一定的请求数,但是通过实际发现master并没有平均分发请求到每个worker,而且集中一段时间到一个worker,另外一段时间又到了另外一个worker。 这样没法做到真正的多进程负载均衡。

Proposal

请问下,有没有办法让master像ngxin轮训那样节点逐个分发。

Additional context

Add any other context or screenshots about the feature request here.

回答

7

node cluster 内置的逻辑

5

是不是 HTTP 请求 keep-alive 了?复用连接会导致请求分布不均匀。

6

而且环境变量 NODE_CLUSTER_SCHED_POLICY 可以控制 master 的转发策略,默认是 rr 即轮训,设置为 none 的话则会采用共享句柄的方式来负载,既有内核控制句柄分发

7

我们node服务是封装在docker里的,有心跳请求,10s,从日志看,心跳基本可以轮训。 127.0.0.1 63491 - [19/Aug/2021:05:54:57.689 +0000] "GET /healthCheck HTTP/1.1" 200 7 0 127.0.0.1 63491 - [19/Aug/2021:05:54:59.616 +0000] "GET /healthCheck HTTP/1.1" 200 7 0 127.0.0.1 63498 - [19/Aug/2021:05:55:07.687 +0000] "GET /healthCheck HTTP/1.1" 200 7 1 127.0.0.1 63498 - [19/Aug/2021:05:55:09.615 +0000] "GET /healthCheck HTTP/1.1" 200 7 1 127.0.0.1 63492 - [19/Aug/2021:05:55:17.692 +0000] "GET /healthCheck HTTP/1.1" 200 7 1 127.0.0.1 63492 - [19/Aug/2021:05:55:19.615 +0000] "GET /healthCheck HTTP/1.1" 200 7 0 127.0.0.1 63491 - [19/Aug/2021:05:55:27.688 +0000] "GET /healthCheck HTTP/1.1" 200 7 0 127.0.0.1 63491 - [19/Aug/2021:05:55:29.622 +0000] "GET /healthCheck HTTP/1.1" 200 7 0 127.0.0.1 63498 - [19/Aug/2021:05:55:37.687 +0000] "GET /healthCheck HTTP/1.1" 200 7 0 127.0.0.1 63498 - [19/Aug/2021:05:55:39.615 +0000] "GET /healthCheck HTTP/1.1" 200 7 0 127.0.0.1 63491 - [19/Aug/2021:05:55:47.687 +0000] "GET /healthCheck HTTP/1.1" 200 7 0 127.0.0.1 63498 - [19/Aug/2021:05:55:49.616 +0000] "GET /healthCheck HTTP/1.1" 200 7 0 127.0.0.1 63498 - [19/Aug/2021:05:55:57.688 +0000] "GET /healthCheck HTTP/1.1" 200 7 0 127.0.0.1 63492 - [19/Aug/2021:05:55:59.615 +0000] "GET /healthCheck HTTP/1.1" 200 7 0 127.0.0.1 63492 - [19/Aug/2021:05:56:07.688 +0000] "GET /healthCheck HTTP/1.1" 200 7 0 127.0.0.1 63491 - [19/Aug/2021:05:56:09.615 +0000] "GET /healthCheck HTTP/1.1" 200 7 0

但是如果遇到高并发的请求,那么可能连续10s,请求都落在一个worker进程中。 63491 - [19/Aug/2021:03:43:08.25 +0000] "GET /s/JUVDUyU HTTP/1.1" 200 67224 59 63491 - [19/Aug/2021:03:43:08.38 +0000] "GET /s/JUVDUyU HTTP/1.1" 200 67224 65 63491 - [19/Aug/2021:03:43:08.96 +0000] "GET /s/JUVDUyU HTTP/1.1" 200 67224 69 63491 - [19/Aug/2021:03:43:08.104 +0000] "GET /s/JUVDUyU HTTP/1.1" 200 67224 63 63491 - [19/Aug/2021:03:43:08.165 +0000] "GET /s/JUVDUyU HTTP/1.1" 200 67224 59 63491 - [19/Aug/2021:03:43:08.175 +0000] "GET /s/JUVDUyU HTTP/1.1" 200 67224 77 63491 - [19/Aug/2021:03:43:08.178 +0000] "GET /s/JUVDUyU HTTP/1.1" 429 47 1 63491 - [19/Aug/2021:03:43:08.179 +0000] "GET /s/JUVDUyU HTTP/1.1" 429 47 0 63491 - [19/Aug/2021:03:43:08.180 +0000] "GET /s/JUVDUyU HTTP/1.1" 429 47 0 63491 - [19/Aug/2021:03:43:08.182 +0000] "GET /s/JUVDUyU HTTP/1.1" 429 47 1 63491 - [19/Aug/2021:03:43:08.183 +0000] "GET /s/JUVDUyU HTTP/1.1" 429 47 0 63491 - [19/Aug/2021:03:43:08.184 +0000] "GET /s/JUVDUyU HTTP/1.1" 429 47 0 63491 - [19/Aug/2021:03:43:08.185 +0000] "GET /s/JUVDUyU HTTP/1.1" 429 47 0 63491 - [19/Aug/2021:03:43:08.187 +0000] "GET /s/JUVDUyU HTTP/1.1" 429 47 1 63491 - [19/Aug/2021:03:43:08.188 +0000] "GET /s/JUVDUyU HTTP/1.1" 429 47 0 63491 - [19/Aug/2021:03:43:08.189 +0000] "GET /s/JUVDUyU HTTP/1.1" 429 47 0 63491 - [19/Aug/2021:03:43:08.190 +0000] "GET /s/JUVDUyU HTTP/1.1" 429 47 0 63491 - [19/Aug/2021:03:43:08.191 +0000] "GET /s/JUVDUyU HTTP/1.1" 429 47 0 63491 - [19/Aug/2021:03:43:08.193 +0000] "GET /s/JUVDUyU HTTP/1.1" 429 47 0 63491 - [19/Aug/2021:03:43:08.194 +0000] "GET /s/JUVDUyU HTTP/1.1" 429 47 0 63491 - [19/Aug/2021:03:43:08.195 +0000] "GET /s/JUVDUyU HTTP/1.1" 429 47 0 63491 - [19/Aug/2021:03:43:08.196 +0000] "GET /s/JUVDUyU HTTP/1.1" 429 47 0 63491 - [19/Aug/2021:03:43:08.198 +0000] "GET /s/JUVDUyU HTTP/1.1" 429 47 1 63491 - [19/Aug/2021:03:43:08.247 +0000] "GET /s/JUVDUyU HTTP/1.1" 200 67224 80 63491 - [19/Aug/2021:03:43:08.250 +0000] "GET /s/JUVDUyU HTTP/1.1" 429 47 0 63491 - [19/Aug/2021:03:43:08.251 +0000] "GET /s/JUVDUyU HTTP/1.1" 429 47 0 63491 - [19/Aug/2021:03:43:08.253 +0000] "GET /s/JUVDUyU HTTP/1.1" 429 47 1 63491 - [19/Aug/2021:03:43:08.254 +0000] "GET /s/JUVDUyU HTTP/1.1" 429 47 0

3

是不是 HTTP 请求 keep-alive 了?复用连接会导致请求分布不均匀。

请问下,你说的这个keep-alive是指外部到master的请求吗?

我们这边性能测试的测试机 把keep-alive去掉,也还是有同样不均的问题。

8

我们node服务是封装在docker里的,有心跳请求,10s,从日志看,心跳基本可以轮训。 但是如果遇到高并发的请求,那么可能连续10s,请求都落在一个worker进程中。

从 cluster 的实现上看,这个是有可能的:

RoundRobinHandle.prototype.handoff = function(worker) {
  if (!this.all.has(worker.id)) {
    return;  // Worker is closing (or has closed) the server.
  }

  const handle = this.handles.shift();

  if (handle === undefined) {
    this.free.set(worker.id, worker);  // Add to ready queue again.
    return;
  }

  const message = { act: 'newconn', key: this.key };

  sendHelper(worker.process, message, handle, (reply) => {
    if (reply.accepted)
      handle.close();
    else
      this.distribute(0, handle);  // Worker is shutting down. Send to another.

    this.handoff(worker);
  });
};

依照这个实现,获取 handle(即新请求)取决于 worker 谁先执行完当前任务就可以从 handles 队列中获取新的请求处理权限

6

换句话说,内核 cluster 实现的 rr 策略,要求 worker 处理的请求数也近似均衡的话,需要每个不同的用户请求处理逻辑的自身耗时近似,既用户请求处理不能有明显的快有慢,这也是为什么你的心跳能在数量上也 rr 的原因。

但是实际业务中显然不太可能做到这样,因此这个 rr 策略是 worker 处理负载的 rr,而非你理解的处理请求数量的 rr。

其实内核的这个实现也是合理的,举个极端的情况,一共两个 worker,总共有两种用户请求,一种耗时 10ms,另一种耗时 10s。

那么如果内核实现是 worker 按照数量 rr 负载,就会出现 worker A 10ms 处理完了快请求,但是由于数量 rr 负载策略,它得等 worker B 处理完 10s 的慢请求后才能处理下一个请求,这个显然不合理。

8

在做一个egg流控插件,每个worker限制一定的请求数

你需要考虑的是,如果请求处理逻辑耗时本身就不一致的话,这个用请求数做限流的需求本身是错误的。

3

换句话说,内核 cluster 实现的 rr 策略,要求 worker 处理的请求数也近似均衡的话,需要每个不同的用户请求处理逻辑的自身耗时近似,既用户请求不能有快有慢,这也是为什么你的心跳能在数量上也 rr 的原因。

但是实际业务中显然不太可能做到这样,因此这个 rr 策略是 worker 处理负载的 rr,而非你理解的处理请求数量的 rr。

其实内核的这个实现也是合理的,举个极端的情况,一共两个 worker,总共有两种用户请求,一种耗时 10ms,另一种耗时 10s。

那么如果内核实现是 worker 按照数量 rr 负载,就会出现 worker A 10ms 处理完了快请求,但是由于数量 rr 负载策略,它得等 worker B 处理完 10s 的慢请求后才能处理下一个请求,这个显然不合理。

你说的这个理解,但是还没到那么极端的情况,平均时延是80ms, 95%的时延是小于140ms。 但是从worker的分配看,间歇性出现大量的请求连续5-10s落在一个worker上。

0

你说的这个理解,但是还没到那么极端的情况,平均时延是80ms, 95%的时延是小于140ms。 但是从worker的分配看,间歇性出现大量的请求连续5-10s落在一个worker上。

你可以给个稳定复现的 demo,把这个问题反馈给 nodejs 官方,这个调度问题和 egg 本身没有关系。

6

在做一个egg流控插件,每个worker限制一定的请求数

你需要考虑的是,如果请求处理逻辑耗时本身就不一致的话,这个用请求数做限流的需求本身是错误的。

请问下 如果想限制master的入口请求数,有没有什么好的办法呢?

4

请问下 如果想限制master的入口请求数,有没有什么好的办法呢?

你可以自己实现一个插件,把处理请求数按照服务器切割存到 redis 里,超过你的请求数/时间段限制,就直接反馈一个错误