Nginx与后端上游服务保持长连接

nginx upstream connection keepalive

Posted by alovn on May 11, 2021

问题

如果使用了Nginx作为反向代理或者负载均衡,在高并发下若发现Nginx服务器上产生了大量TIME-WAIT的连接,那就需要检查下Nginx与后端的上游服务器之间是否保持了长连接。

默认情况下,从客户端过来的长连接请求会被转换成短连接发送给后端的上游服务器,即upstream。

可通过tcpdump抓包观察或者配置Nginx upstream转发到 nc 监听的端口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
nc -l 1111
GET /api/ HTTP/1.0
Host: 172.16.10.14
X-Real-IP: 172.16.10.3
X-Forwarded-For: 172.16.10.3
X-Forwarded-Proto: http
Accept-Encoding: gzip
Connection: close
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
X-Trace-Id: fbbb49ab-b66e-432a-bdc4-252663b5582f

可以看到:Nginx在反向代理的时候,默认使用了HTTP1.0协议向后端服务器发送请求,同时 HTTP Header 中使用了 Connection:Close 通知后端服务器主动关闭连接。HTTP1.0和HTTP1.1的其中一个不同之处就是:HTTP1.0不支持HTTP keep-alive。

这样会导致任何一个客户端的请求都在后端服务器上产生了一个TIME-WAIT状态的连接,一段时间后会自动释放。如果并发较大,服务器上会产生大量的TIME-WAIT,同时占用大量端口,如果释放不及时甚至会耗尽端口从而阻塞。

TIME-WAIT 产生原因与作用

TCP四次挥手过程:

sequenceDiagram
  autonumber
  Note left of Client: 主动关闭端
  Client ->> Server: FIN=1, seq=x (我要和你分手)
  Server ->> Client: ACK=1, ack=x+1, seq=y (好,等我把我的东西收拾完!)
  Server ->> Client: FIN=1, ack=x+1, seq=z (我的东西收拾完了,咱们分手吧!)
  Client -) Server: ACK=1, ack=z+1, seq=w (好!拜拜!)
  Client -->> Client: TiME-WAIT (等待2MSL)

TCP连接是全双工的,因此每个方向都必须单独进行关闭。在TCP关闭连接四次挥手时,最后的ACK是主动关闭端发出的,随后进入 TIME-WAIT 状态,等待 2MSL(2 * Maximum Segment Lifetime,两倍的报文段最大存活时间) ,这是任何报文段在被丢弃前能在网络中存在的最长时间,常用值有30秒、1分钟和2分钟。网络并不总是可靠的,如果客户端最后发出的这个ACK报文丢失,服务器(被关闭端)接收不到ACK将不断重发FIN(超时一定时间后,停止发送,主动断开)。所以客户端为了确保收到ACK,会设置一个定时器进入TIME-WAIT状态等待2MSL时间。如果在TIME-WAIT期间收到了服务器端的FIN报文,客户端会重新记时并再次等待2MSL时间,如果这期间没有再收到服务器端的FIN报文,就认为服务器端已经收到了最后发出的ACK报文,然后客户端就进入CLOSED状态了。

如果Nginx与后端的上游服务器之间是短连接,就会频繁的创建、关闭连接,并占用端口一段时间,所以要尽量使用长连接。

Nginx配置

为了使Nginx连接到upstream支持Keep-alive,我们需要在Nginx上启用HTTP1.1的向后端发送请求,同时把nginx默认传递给upsteam connection:close的行为去掉。配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
upstream backend {
    server 172.16.10.14:1111;
    keepalive 50;
}

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

其它

如果后端服务有使用 http client 调用其它HTTP服务(第三方http接口,或者内部微服务),也有可能会产生大量TIME-WAIT的问题。那么也可以考虑http client使用长连接,这就需要在代码层面处理了。

参考官方文档

  1. 浏览器到nginx长连接

  2. nginx到tomcat长连接