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 -> 如果有对慢速请求的请求,下一个将是快速响应)。

如果您从不同的客户端发送请求,同样适用。这也会起作用。