http 服务端如何发觉客户端已经 timeout 了?
假设有一种场景:
一个 http server
可以同时接受无数个请求,而每秒只能处理一个 request
,客户端设置的 timeout
为 30
秒(假设连接超时和读取超时的 timeout 阈值都是 30 秒)
此事客户端一次性发出了 1000
个请求,服务端都接受了这 1000
个请求,然后慢慢做,但是 30
秒只能做 30
个 request
的 response
,剩下的 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 代码中主动 abort
和 timeout
。
但是因为浏览器会有 max:6
的限制,所以如果请求没有发送出去的话,那么服务端不会继续处理。
补充一下环境
客户端:浏览器 chrome
服务端:本地环境 node express
做了两个延时任务
# 不占用 CPUawait new Promise(function(resolve){
setTimeout(resolve, sleep)
})
# 占用 CPUawait new Promise(function(resolve){
while(Date.now() - startTime < sleep){
}
resolve()
})
占用 CPU 测试案例
可以看到,请求在前面并没有发出去,并且中间还有一个 TTFB 时间。
从服务端的响应看回来,只是当前 cpu 不占用时才会收到新的请求处理。
不占用 CPU 测试案例
可以看到,只是因为浏览器的并发限制,所有请求都会被服务端接收,只不过因为其中有异步处理,一直没收到回调而已。
测试中途断开连接(不占用 CPU 测试案例)
已发起,但是中途断开(第三个请求)
可以看到第三个请求前端是已经 cancel 了,并且第七个在取消的时候就已经正式的连通到了服务端。
在写的时候突然想起来,之前证明过一个类似的问题,是 chrome 请求数超过 6 的问题。
从服务端的日志截图可以看到,服务端在最后断开后仍然在处理
已发起,但是因为浏览器限制导致服务器没收到,客户端触发 cancel
从服务端截图可以看到,并不会收到请求。
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)
已发起,但是因为服务端限制,导致服务器没有收到,客户端触发 cancel
可以看到,虽然只有前三个没取消,但是前六个还是像服务器发起请求了。
已发起,但是因为服务端限制,导致服务器没有收到,客户端触发 timeout
所有请求还是打到了服务端。
回答:
设想一个场景:一个医院有10个医生,一个医生一次只能看一个病人,这样最多同时接待10个病人,但是外面有1000个病人排队等着要看病,不管你有多少病人,你必须要排队挂号,拿到号之后等着,等叫到你的时候你才能进去,如果你等不及,你走了,那是你的问题,与医院无关。现实生活中是不是这样?
应用服务器就相当于是医院里的医生,服务器前面的Nginx就相当于是挂号处,http请求相当于是病人,任何请求首先到达的是Nginx,他们都需要在Nginx这里排队领号,领到号之后才能看病,领不到号你要么继续等待,要么自行离开,不管你多少个请求,没有号的病人不可能直接打扰到后面的服务器。
这个技术就叫做令牌桶(感兴趣的可以自行百度“Nginx 令牌桶”):
所以作为医生的服务端来说,我不需要关心外面有多少病人,我也不关心有多少病人等不耐烦而离开了,反正我一分钟看一个,有号的我就看,没号的外面Nginx已经帮我拦截了,这样服务器没有任何压力,就算只有一个医生,外面堆了一万个病人,医生也是感觉不到的。如果实在觉得处理速度慢,医院可以多请几个医生,但不管几个医生,压力只在挂号处,不在医生这里。
已参与了 SegmentFault 思否社区 10 周年「问答」打卡 ,欢迎正在阅读的你也加入。
回答:
当设置超时,前端请求超时,会及时断开,后端会处理后面的求请,不会一直等着,导致资源浪费
以上是 http 服务端如何发觉客户端已经 timeout 了? 的全部内容, 来源链接: utcz.com/p/938482.html