http 服务端如何发觉客户端已经 timeout 了?

假设有一种场景:
一个 http server 可以同时接受无数个请求,而每秒只能处理一个 request,客户端设置的 timeout30 秒(假设连接超时和读取超时的 timeout 阈值都是 30 秒)

此事客户端一次性发出了 1000 个请求,服务端都接受了这 1000 个请求,然后慢慢做,但是 30 秒只能做 30requestresponse,剩下的 970 个客户端直接等不及 timeout(这个时候,既然 timeout 了,客户端如果不是 keepalive 应该直接四次挥手了吧?但是也可以不四次挥手吧)

等到第 31 秒的,server 端继续做剩下的 970 个的时候,发现 response 发过去没有客户端要了?!?!

这咋办?

这样有两个问题:

  • server 端的资源被 970 个无效请求堵塞 970 秒,导致服务不可用
  • server 消费无用的任务,造成执行成本浪费

显然我们要保证,这种情况不发生!

那业内的解决方案是什么呢?

我想到一种办法:

  • 开始处理 requets 之前,都是用 http 协议或者更下面的 tcp 去问问客户端:喂,你(指客户端)还在不在听啊?
  • 得到 ok 后在干活
  • 得到 no ok 就直接抛弃 request

但是这种方式很 low,因为要网络 io,等于每个 request 之前都要走一次网络 io,开销太大了

nginx、apache httpd、tomcat、gunicorn 等等都是采用什么解决方案?

总不能也是在 start_request 之前都去问问客户端还在不在等 response 吧!

客户端 timeout 之后,一定会发起四次挥手吗?这个应该是不一定的吧?比如 keepalve 的长连接场景,客户端可能还行复用这个 http 连接呢(或者说 tcp 连接?我不确定 http 连接是不是等于 tcp 连接,多个 http 连接可以复用一个 tcp 连接吗?)


回答:

先放结论,浏览器端发起的请求都会被服务端继续处理,即使 js 代码中主动 aborttimeout

但是因为浏览器会有 max:6 的限制,所以如果请求没有发送出去的话,那么服务端不会继续处理。

补充一下环境
客户端:浏览器 chrome
服务端:本地环境 node express


做了两个延时任务

# 不占用 CPU

await new Promise(function(resolve){

setTimeout(resolve, sleep)

})

# 占用 CPU

await new Promise(function(resolve){

while(Date.now() - startTime < sleep){

}

resolve()

})

占用 CPU 测试案例

可以看到,请求在前面并没有发出去,并且中间还有一个 TTFB 时间。

从服务端的响应看回来,只是当前 cpu 不占用时才会收到新的请求处理。

http 服务端如何发觉客户端已经 timeout 了?

不占用 CPU 测试案例

可以看到,只是因为浏览器的并发限制,所有请求都会被服务端接收,只不过因为其中有异步处理,一直没收到回调而已。

http 服务端如何发觉客户端已经 timeout 了?

测试中途断开连接(不占用 CPU 测试案例)

已发起,但是中途断开(第三个请求)

可以看到第三个请求前端是已经 cancel 了,并且第七个在取消的时候就已经正式的连通到了服务端。

http 服务端如何发觉客户端已经 timeout 了?

在写的时候突然想起来,之前证明过一个类似的问题,是 chrome 请求数超过 6 的问题。

http 服务端如何发觉客户端已经 timeout 了?

从服务端的日志截图可以看到,服务端在最后断开后仍然在处理

已发起,但是因为浏览器限制导致服务器没收到,客户端触发 cancel

http 服务端如何发觉客户端已经 timeout 了?

http 服务端如何发觉客户端已经 timeout 了?

从服务端截图可以看到,并不会收到请求。


demo 中的例子好像有点问题,因为是个微任务,所以有可能给了浏览器处理的时机,所以后面我贴了个同步终止的例子

list = []

for(var i = 0; i < 10; i++){

setTimeout((i)=>{

let xhr = new XMLHttpRequest();

xhr.open('get', `http://localhost:9102/?sleep=1000&clientKey=${i}`)

xhr.send();

list.push(xhr)

// if(i > 3){

// setTimeout(()=>xhr.abort(), 300)

// // setTimeout(()=>xhr.abort(), 300 - i * 10)

// }

}, i * 10, i)

}

setTimeout(()=>{

list.slice(3).reverse().forEach(v=>v.abort())

}, 300)

http 服务端如何发觉客户端已经 timeout 了?

已发起,但是因为服务端限制,导致服务器没有收到,客户端触发 cancel

可以看到,虽然只有前三个没取消,但是前六个还是像服务器发起请求了。
http 服务端如何发觉客户端已经 timeout 了?

已发起,但是因为服务端限制,导致服务器没有收到,客户端触发 timeout

http 服务端如何发觉客户端已经 timeout 了?

http 服务端如何发觉客户端已经 timeout 了?

所有请求还是打到了服务端。


回答:

设想一个场景:一个医院有10个医生,一个医生一次只能看一个病人,这样最多同时接待10个病人,但是外面有1000个病人排队等着要看病,不管你有多少病人,你必须要排队挂号,拿到号之后等着,等叫到你的时候你才能进去,如果你等不及,你走了,那是你的问题,与医院无关。现实生活中是不是这样?

应用服务器就相当于是医院里的医生,服务器前面的Nginx就相当于是挂号处,http请求相当于是病人,任何请求首先到达的是Nginx,他们都需要在Nginx这里排队领号,领到号之后才能看病,领不到号你要么继续等待,要么自行离开,不管你多少个请求,没有号的病人不可能直接打扰到后面的服务器。

这个技术就叫做令牌桶(感兴趣的可以自行百度“Nginx 令牌桶”):

http 服务端如何发觉客户端已经 timeout 了?

所以作为医生的服务端来说,我不需要关心外面有多少病人,我也不关心有多少病人等不耐烦而离开了,反正我一分钟看一个,有号的我就看,没号的外面Nginx已经帮我拦截了,这样服务器没有任何压力,就算只有一个医生,外面堆了一万个病人,医生也是感觉不到的。如果实在觉得处理速度慢,医院可以多请几个医生,但不管几个医生,压力只在挂号处,不在医生这里。


已参与了 SegmentFault 思否社区 10 周年「问答」打卡 ,欢迎正在阅读的你也加入。


回答:

当设置超时,前端请求超时,会及时断开,后端会处理后面的求请,不会一直等着,导致资源浪费

以上是 http 服务端如何发觉客户端已经 timeout 了? 的全部内容, 来源链接: utcz.com/p/938482.html

回到顶部