高并发下场景下,使用nodejs产生的问题
发布于 5 年前 作者 FantasyGao 9114 次浏览 来自 问答

问题描述

目前有一个需求,需求中涉及一个比较耗时的操作,这时候产生了两种方案,请各位大佬指导一下。

  1. 这个请求大概耗时在20秒左右,发起请求后前端一直转圈圈等待返回。 质疑点:当大量用户进来后,http的连接数限制,会不会被hold住,后面的服务器请求一直等待排队。

  2. 设计两个请求,并且在缓存中设置一个标志量,第一个请求进来触发耗时操作,并且立即返回前端”处理中“状态,然后在前端加新的轮询请求,请求缓存中的标志量,返回第一个请求的操作结果,这样可以及时释放http请求, 质疑点:前端要多好几次轮询开销,而且如果轮询间隔时间不合适,体验也会变差。

大佬们给一下意见?希望能从nodejs和java对比一下,那种更合适处理这种场景。

18 回复

如果没有空闲 worker 的话,那么接下来的请求都会进 pending 队列,等待出现空闲 worker 来消化 pending 队列。不好直接从 node 和 Java 的角度来对比,而是对比 node 的框架和 Java 框架。不过应该都是一样的。

如果非要在这个问题上对比 node 和 Java 的话,我感觉如果这个耗时操作放到 Java 上能变成 2 秒会比较有意义

@hsiaosiyuan0 我是这样理解的,由于nodejs的高并发与异步非阻塞特点,nodejs在创建的单进程服务器能接受4000到5000并发量的请求连接处理,把多进程(8个)部署下,在1000多的并发量下,不会导致请求排队,使用第一种方式应该可以的没问题。

@FantasyGao 为什么不会排队呢?如果并发 1000 个请求,而 workers 数量为 8,处理一个请求需要占用一个 worker 大概 20s,那么队列里面至少也得有 992 个请求 pending

@FantasyGao node 是异步非阻塞的,但是你的业务按方式 1 来处理是阻塞的,按方式 2 来是异步非阻塞的

@hsiaosiyuan0 1000 个请求不是同步阻塞的计算任务,也是一些异步调用的逻辑,请求进来之后调用其他系统了,主线程立即释放的那种情况,这种怎么考虑下。

@FantasyGao 虽然是调用的外部计算资源,但是方式 1 的设计,不就是等待外部计算结束了才返回给客户端状态吗,这个时间内,连接不是一直打开的状态,客户端一直等待吗

@hsiaosiyuan0 假设只有一个worker,同一时间也可以不止接收一个请求,虽然前面的请求(需要20s)还没返回,依旧可以接收新的请求,因为http请求是异步的。 workers数量为8,并发1000个请求,不代表会有992个pending。

方案1,看场景,如果是CPU密集型,那么20s的请求复杂处理对于node来说是不适合的。而如果这20s是交给了其他进程或者其他服务器之类的异步处理,那么理论上能达到的连接数与系统内存有关。并不会造成http请求被hold住,这个你简单起一个http server便能测试:

const util = require('util');
const Koa = require('koa');
const app = new Koa();

app.use(async ctx => {
  console.log(new Date() + ' 处理请求');
  await util.promisify(setTimeout)(20000);
  console.log('处理请求处理完成');
  ctx.body = { 'hello': 'world' };
});
app.listen(3000);
console.log('listen at 3000');

启动后,同时发送多个请求,你可以只起一个进程,观察请求的处理过程。

@iceyang

假设只有一个worker,同一时间也可以不止接收一个请求

我也没说不能接受到请求啊

质疑点:当大量用户进来后,http的连接数限制,会不会被hold住,后面的服务器请求一直等待排队

如果 20s 的操作,不是 CPU 密集型的,那对客户端的请求来说就不需要等待 20s 了嘛?如果还要继续等待 20s,这个业务不就是阻塞的嘛?

@hsiaosiyuan0

我大概明白我们的差异源头在哪里了。

质疑点:当大量用户进来后,http的连接数限制,会不会被hold住,后面的服务器请求一直等待排队

对于「会不会被hold住」这一点,我是根据楼主说的「当大量用户进来后」,推测他想知道的是不是指 服务端无法再响应新的请求。所以我会说如果是IO密集型,只是简单逻辑的话,理论上来讲,能建立的连接数与内存有关。

你说到的20s的业务阻塞,是针对客户端的这一请求来讲,我认可,这是必然的,不管服务器如何处理,这20s就是请求的总时间,这是针对客户端的这一请求而言。 针对并发量,一般来说并发请求应该都是不同的http请求,所以某一客户端的请求发生业务阻塞 跟 服务端的无法响应 没有必然关系。

结论还是前面的原因,既然有歧义,那么楼主明确下就可以解决问题了。

@iceyang

对于「会不会被hold住」这一点,我是根据楼主说的「当大量用户进来后」,推测他想知道的是不是指 服务端无法再响应新的请求。所以我会说如果是IO密集型,只是简单逻辑的话,理论上来讲,能建立的连接数与内存有关。

就算是 CPU 密集型,连接数的约束条件也是系统 fd limit 或者内存量。master 进程接收客户端连接 dispatch 给空闲 worker 进程,能做到这样 master 进程里面肯定用的是 NIO,worker 里面是不是 CPU 密集型的,并不会导致 master 不能接收新的连接。我之前的 worker 例子不是说 node 就是这样实现的,因为楼主说对比 Java 和 node,我随便举个通用简单的例子 。我的意思是即使换了 Java,如果 20s 还是要等待,没有啥可以对比的,结果都一样。20s 的耗时操作不管操作本身是不是 CPU 密集型的,都会导致该连接 20s 内无法被释放,而本身问题又带有高并发的属性,也就是说 20s 所有建立的请求都无法被释放,很快就达到 fd limit 或者 OOM

@iceyang @hsiaosiyuan0 首先感谢两位大佬的回答,我再描述一下问题。 目前有一个操作大概耗时20s左右,后台接口由JAVA的spring框架提供,后端的同学指出如果前端发起这个操作请求后,前端一直loading20s左右等待这个接口返回,那意味着后端要将这个请求handle住。在这个请求从后端接到直到处理完成后reponse前端这个时间段内,由于http连接数的限制,会导致其他的请求排队不能得到处理。

我不太理解java的这个handle,但是我觉得如果使用nodejs的这种高并发的异步模型,并发下的请求都会得到处理,在一定量的前提下(不是大的超过机器限制),并不会有请求排队的情况。

@FantasyGao 这个场景下,你后端同学的意思估计是这样:

假设:

  1. 当前环境下(服务器也好,语言也好)http连接数只能达到1000
  2. 接收的请求都能正常处理,也就是20s返回

那么: 在并发超过1000的情况下,超过1000的请求肯定需要等到前面1000个请求的某一个处理完成后,才能处理第1001个。

所以 @hsiaosiyuan0 才说,这跟语言是无关的。 不管多线程也好,事件异步也好,逃不出请求数的限制。

这里还有另外一个前提,就是并发就算达到1000,服务器都能以正常的速度处理请求。

@iceyang 我可以理解为这个请求最大连接数不是语言层面的事,是服务器自身设置控制的吗?那我觉得nodejs本身是能承载接收的,服务器做这个控制的意义在哪里,大佬解惑

@FantasyGao 根据你http的场景,可以这样搜索下:

关键字:tcp、最大连接数

你会得到你要的答案的。

服务器做这个控制的意义在哪里

建立连接是需要占用资源的,资源并不是无限的

转自全栈框架CabloyJS中的Kitchen-sink progressbar.2019-07-21 20_57_06.gif 1.png 2.png

(1)、client最大tcp连接数 client每次发起tcp连接请求时,除非绑定端口,通常会让系统选取一个空闲的本地端口(local port),该端口是独占的,不能和其他tcp连接共享。 tcp端口的数据类型是unsigned short,因此本地端口个数最大只有65536,端口0有特殊含义,不能使用,这样可用端口最多只有65535,所以在全部作为client端的情况下,最大tcp连接数为65535,这些连接可以连到不同的server ip。

(2)、server最大tcp连接数 server通常固定在某个本地端口上监听,等待client的连接请求。不考虑地址重用(unix的SO_REUSEADDR选项)的情况下,即使server端有多个ip,本地监听端口也是独占的, 因此server端tcp连接4元组中只有remote ip(也就是client ip)和remote port(客户端port)是可变的,因此最大tcp连接为客户端ip数×客户端port数,对IPV4,不考虑ip地址分类等因素, 最大tcp连接数约为2的32次方(ip数)×2的16次方(port数),也就是server端单机最大tcp连接数约为2的48次方。

而tcp连接受到文件句柄和端口限制,这直接限制tcp的连接数量

回到顶部