nginx 阻塞请求直到当前请求完成
nginx blocking request till current request finishes
将我的问题归结为最简单的问题:我有一个简单的 Flask 网络服务器,它有一个像这样的 GET 处理程序:
@app.route('/', methods=['GET'])
def get_handler():
t = os.environ.get("SLOW_APP")
app_type = "Fast"
if t == "1":
app_type = "Slow"
time.sleep(20)
return "Hello from Flask, app type = %s" % app_type
我 运行 这个应用程序在两个不同的端口上:一个没有在端口 8000 上设置 SLOW_APP 环境变量,另一个在端口 8001 上设置了 SLOW_APP 环境变量。
接下来我有一个 nginx 反向代理,它的上游有这两个 appserver 实例。我 运行 一切都使用 docker 所以我的 nginx conf 看起来像这样:
upstream myproject {
server host.docker.internal:8000;
server host.docker.internal:8001;
}
server {
listen 8888;
#server_name www.domain.com;
location / {
proxy_pass http://myproject;
}
}
除了如果我打开两个浏览器 windows 并输入 localhost,它会首先访问慢速服务器需要 20 秒,在此期间第二个浏览器似乎阻止等待第一个请求结束。最终我看到第一个请求由“慢”服务器提供服务,第二个请求由“快速”服务器提供服务(没有 time.sleep())。为什么 nginx 似乎会阻止第二个请求直到第一个请求完成?
No, if the first request goes to the slow server (where it takes 20 sec) and during that delay if I make a request again from the browser it goes to the second server but only after the first is finished.
我与我们的工程团队就此进行了合作,可以分享以下见解:
我们的实验室环境
Lua
load_module modules/ngx_http_lua_module-debug.so;
...
upstream app {
server 127.0.0.1:1234;
server 127.0.0.1:2345;
}
server {
listen 1234;
location / {
content_by_lua_block {
ngx.log(ngx.WARN, "accepted by fast")
ngx.say("accepted by fast")
}
}
}
server {
listen 2345;
location / {
content_by_lua_block {
ngx.log(ngx.WARN, "accepted by slow")
ngx.say("accepted by slow")
ngx.sleep(5);
}
}
}
server {
listen 80;
location / {
proxy_pass http://app;
}
}
这与我们将流量代理到的另一个第 3 方应用程序的设置相同。但是我已经用你的问题中共享的 NGINX 配置和两个基于 NodeJS 的应用程序作为上游进行了相同的测试。
NodeJS
正常
const express = require('express');
const app = express();
const port = 3001;
app.get ('/', (req,res) => {
res.send('Hello World')
});
app.listen(port, () => {
console.log(`Example app listening on ${port}`)
})
慢
const express = require('express');
const app = express();
const port = 3002;
app.get ('/', (req,res) => {
setTimeout( () => {
res.send('Hello World')
}, 5000);
});
app.listen(port, () => {
console.log(`Example app listening on ${port}`)
})
测试
由于我们使用的是 NGINX OSS,因此负载平衡协议将是 RoundRobin (RR)。我们使用 ap
从另一台服务器进行的第一次测试。结果:
Concurrency Level: 10
Time taken for tests: 25.056 seconds
Complete requests: 100
Failed requests: 0
Total transferred: 17400 bytes
HTML transferred: 1700 bytes
Requests per second: 3.99 [#/sec] (mean)
Time per request: 2505.585 [ms] (mean)
Time per request: 250.559 [ms] (mean, across all concurrent requests)
Transfer rate: 0.68 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.7 0 5
Processing: 0 2505 2514.3 5001 5012
Waiting: 0 2504 2514.3 5001 5012
Total: 1 2505 2514.3 5001 5012
Percentage of the requests served within a certain time (ms)
50% 5001
66% 5005
75% 5007
80% 5007
90% 5010
95% 5011
98% 5012
99% 5012
100% 5012 (longest request)
50% 的请求速度很慢。这完全没问题,因为我们有一个“慢”实例。与 curl
相同的测试。同样的结果。基于 NGINX 服务器的 debug-log,我们看到请求在传入时得到处理,并被发送到慢速或快速后端(基于循环法)。
2021/04/08 15:26:18 [debug] 8995#8995: *1 get rr peer, try: 2
2021/04/08 15:26:18 [debug] 8995#8995: *1 get rr peer, current: 000055B815BD4388 -100
2021/04/08 15:26:18 [debug] 8995#8995: *4 get rr peer, try: 2
2021/04/08 15:26:18 [debug] 8995#8995: *4 get rr peer, current: 000055B815BD4540 0
2021/04/08 15:26:18 [debug] 8995#8995: *5 get rr peer, try: 2
2021/04/08 15:26:18 [debug] 8995#8995: *5 get rr peer, current: 000055B815BD4388 -100
2021/04/08 15:26:18 [debug] 8995#8995: *7 get rr peer, try: 2
2021/04/08 15:26:18 [debug] 8995#8995: *7 get rr peer, current: 000055B815BD4540 0
2021/04/08 15:26:18 [debug] 8995#8995: *10 get rr peer, try: 2
2021/04/08 15:26:18 [debug] 8995#8995: *10 get rr peer, current: 000055B815BD4388 -100
2021/04/08 15:26:18 [debug] 8995#8995: *13 get rr peer, try: 2
2021/04/08 15:26:18 [debug] 8995#8995: *13 get rr peer, current: 000055B815BD4540 0
2021/04/08 15:26:18 [debug] 8995#8995: *16 get rr peer, try: 2
2021/04/08 15:26:18 [debug] 8995#8995: *16 get rr peer, current: 000055B815BD4388 -100
2021/04/08 15:26:18 [debug] 8995#8995: *19 get rr peer, try: 2
2021/04/08 15:26:18 [debug] 8995#8995: *19 get rr peer, current: 000055B815BD4540 0
因此,这意味着“nginx 阻塞请求直到当前请求完成”的行为在实例上不可重现。但我能够在 Chrome 浏览器中重现您的问题。点击慢速实例会让另一个浏览器 window 等待第一个浏览器得到响应。在客户端进行一些内存分析和调试后,我发现了浏览器的连接池。
https://www.chromium.org/developers/design-documents/network-stack
浏览器使用相同的、已经建立的与服务器的连接。如果此连接被等待请求占用(相同的数据,相同的 cookie...),它将不会从池中打开新连接。它将等待第一个请求完成。您可以通过向请求添加 cache-buster 或新的 header 新 cookie 来解决此问题。类似于:
http://10.172.1.120:8080/?ofdfu9aisdhffadf
。当您在另一个浏览器中等待响应时,请在新浏览器 window 中发送它。这将显示立即响应(假定没有对后端的其他请求,因为基于 RR -> 如果有对慢速请求的请求,下一个将是快速响应)。
如果您从不同的客户端发送请求,同样适用。这也会起作用。
将我的问题归结为最简单的问题:我有一个简单的 Flask 网络服务器,它有一个像这样的 GET 处理程序:
@app.route('/', methods=['GET'])
def get_handler():
t = os.environ.get("SLOW_APP")
app_type = "Fast"
if t == "1":
app_type = "Slow"
time.sleep(20)
return "Hello from Flask, app type = %s" % app_type
我 运行 这个应用程序在两个不同的端口上:一个没有在端口 8000 上设置 SLOW_APP 环境变量,另一个在端口 8001 上设置了 SLOW_APP 环境变量。 接下来我有一个 nginx 反向代理,它的上游有这两个 appserver 实例。我 运行 一切都使用 docker 所以我的 nginx conf 看起来像这样:
upstream myproject {
server host.docker.internal:8000;
server host.docker.internal:8001;
}
server {
listen 8888;
#server_name www.domain.com;
location / {
proxy_pass http://myproject;
}
}
除了如果我打开两个浏览器 windows 并输入 localhost,它会首先访问慢速服务器需要 20 秒,在此期间第二个浏览器似乎阻止等待第一个请求结束。最终我看到第一个请求由“慢”服务器提供服务,第二个请求由“快速”服务器提供服务(没有 time.sleep())。为什么 nginx 似乎会阻止第二个请求直到第一个请求完成?
No, if the first request goes to the slow server (where it takes 20 sec) and during that delay if I make a request again from the browser it goes to the second server but only after the first is finished.
我与我们的工程团队就此进行了合作,可以分享以下见解:
我们的实验室环境
Lua
load_module modules/ngx_http_lua_module-debug.so;
...
upstream app {
server 127.0.0.1:1234;
server 127.0.0.1:2345;
}
server {
listen 1234;
location / {
content_by_lua_block {
ngx.log(ngx.WARN, "accepted by fast")
ngx.say("accepted by fast")
}
}
}
server {
listen 2345;
location / {
content_by_lua_block {
ngx.log(ngx.WARN, "accepted by slow")
ngx.say("accepted by slow")
ngx.sleep(5);
}
}
}
server {
listen 80;
location / {
proxy_pass http://app;
}
}
这与我们将流量代理到的另一个第 3 方应用程序的设置相同。但是我已经用你的问题中共享的 NGINX 配置和两个基于 NodeJS 的应用程序作为上游进行了相同的测试。
NodeJS
正常
const express = require('express');
const app = express();
const port = 3001;
app.get ('/', (req,res) => {
res.send('Hello World')
});
app.listen(port, () => {
console.log(`Example app listening on ${port}`)
})
慢
const express = require('express');
const app = express();
const port = 3002;
app.get ('/', (req,res) => {
setTimeout( () => {
res.send('Hello World')
}, 5000);
});
app.listen(port, () => {
console.log(`Example app listening on ${port}`)
})
测试
由于我们使用的是 NGINX OSS,因此负载平衡协议将是 RoundRobin (RR)。我们使用 ap
从另一台服务器进行的第一次测试。结果:
Concurrency Level: 10
Time taken for tests: 25.056 seconds
Complete requests: 100
Failed requests: 0
Total transferred: 17400 bytes
HTML transferred: 1700 bytes
Requests per second: 3.99 [#/sec] (mean)
Time per request: 2505.585 [ms] (mean)
Time per request: 250.559 [ms] (mean, across all concurrent requests)
Transfer rate: 0.68 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.7 0 5
Processing: 0 2505 2514.3 5001 5012
Waiting: 0 2504 2514.3 5001 5012
Total: 1 2505 2514.3 5001 5012
Percentage of the requests served within a certain time (ms)
50% 5001
66% 5005
75% 5007
80% 5007
90% 5010
95% 5011
98% 5012
99% 5012
100% 5012 (longest request)
50% 的请求速度很慢。这完全没问题,因为我们有一个“慢”实例。与 curl
相同的测试。同样的结果。基于 NGINX 服务器的 debug-log,我们看到请求在传入时得到处理,并被发送到慢速或快速后端(基于循环法)。
2021/04/08 15:26:18 [debug] 8995#8995: *1 get rr peer, try: 2
2021/04/08 15:26:18 [debug] 8995#8995: *1 get rr peer, current: 000055B815BD4388 -100
2021/04/08 15:26:18 [debug] 8995#8995: *4 get rr peer, try: 2
2021/04/08 15:26:18 [debug] 8995#8995: *4 get rr peer, current: 000055B815BD4540 0
2021/04/08 15:26:18 [debug] 8995#8995: *5 get rr peer, try: 2
2021/04/08 15:26:18 [debug] 8995#8995: *5 get rr peer, current: 000055B815BD4388 -100
2021/04/08 15:26:18 [debug] 8995#8995: *7 get rr peer, try: 2
2021/04/08 15:26:18 [debug] 8995#8995: *7 get rr peer, current: 000055B815BD4540 0
2021/04/08 15:26:18 [debug] 8995#8995: *10 get rr peer, try: 2
2021/04/08 15:26:18 [debug] 8995#8995: *10 get rr peer, current: 000055B815BD4388 -100
2021/04/08 15:26:18 [debug] 8995#8995: *13 get rr peer, try: 2
2021/04/08 15:26:18 [debug] 8995#8995: *13 get rr peer, current: 000055B815BD4540 0
2021/04/08 15:26:18 [debug] 8995#8995: *16 get rr peer, try: 2
2021/04/08 15:26:18 [debug] 8995#8995: *16 get rr peer, current: 000055B815BD4388 -100
2021/04/08 15:26:18 [debug] 8995#8995: *19 get rr peer, try: 2
2021/04/08 15:26:18 [debug] 8995#8995: *19 get rr peer, current: 000055B815BD4540 0
因此,这意味着“nginx 阻塞请求直到当前请求完成”的行为在实例上不可重现。但我能够在 Chrome 浏览器中重现您的问题。点击慢速实例会让另一个浏览器 window 等待第一个浏览器得到响应。在客户端进行一些内存分析和调试后,我发现了浏览器的连接池。
https://www.chromium.org/developers/design-documents/network-stack
浏览器使用相同的、已经建立的与服务器的连接。如果此连接被等待请求占用(相同的数据,相同的 cookie...),它将不会从池中打开新连接。它将等待第一个请求完成。您可以通过向请求添加 cache-buster 或新的 header 新 cookie 来解决此问题。类似于:
http://10.172.1.120:8080/?ofdfu9aisdhffadf
。当您在另一个浏览器中等待响应时,请在新浏览器 window 中发送它。这将显示立即响应(假定没有对后端的其他请求,因为基于 RR -> 如果有对慢速请求的请求,下一个将是快速响应)。
如果您从不同的客户端发送请求,同样适用。这也会起作用。