Nginx 造成 Upstream 服务器大量 TIME_WAIT 连接

网络拓扑

1
[JMeter] -> [ Nginx ] ----> [ Web Server ]

现象

在压力测试期间,在 Web Server 服务器存在大量来自 Nginx 的 TIME_WAIT 连接:

1
2
3
$ netstat -antpl | awk '{print $5, $6}' | sed 's/:[[:print:]]* /\t/g' | sort | uniq -c | sort -rn
  30020 TIME_WAIT <nginx-ip>
     ...

TIME_WAIT 是在 TCP 协议 4 次挥手过程中的中间状态,过一段时间都会消失。

分析

这里存在两个疑点:

  1. TIME_WAIT 状态的连接数量超出了 JMeter 的并发数(500)
  2. 而 JMeter 脚本里都是开启了 Keep-Alive 的

这个现象看上去就像是 Nginx -> Web Server 的连接没有 Keep Alive,导致每次请求都重新建立了连接然后又断开。

查看 Nginx 配置文件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
upstream web_server {
    server <ip1>:8080 max_fails=20;
    keepalive 500;
}

map $http_upgrade $connection_upgrade {
    default Upgrade;
    ''      close;
}

server {
    listen 80;
    location / {
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Port $server_port;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass http://web_server;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_read_timeout 900s;
        proxy_buffering off;
    }
}

可以看到,已经配置了 keepalive 指令,但是看上去没有生效。

不过查看 keepalive 指令的文档发现,它需要配合把 Connection 请求头设置为 "" 才可以使用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
upstream http_backend {
    ...
    keepalive 16;
}

server {
    ...
    location /http/ {
        ...
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        ...
    }
}

而实际上的 Connection 是这样的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
map $http_upgrade $connection_upgrade {
    default Upgrade;
    ''      close;
}

server {
    listen 80;
    location / {
        ...
        proxy_set_header Connection $connection_upgrade;
        ...
    }
}

为什么这样配置呢?这是抄了 Nginx 关于代理 Websocket 的一篇官方博客

根据上面的配置这个 Connection 实际上的值是 close,所以才会每次请求都会断开连接,然后新的请求又创建连接,然后导致大连 TIME_WAIT 的 TCP 连接。

结论

修改 map 指令,改成下面这样问题解决:

1
2
3
4
map $http_upgrade $connection_upgrade {
    default Upgrade;
    ''      '';
}

版权

评论