1. 现象
Nginx反向代理了一个Java服务,QPS大概是200,问题发生时的Nginx配置:
location / {
proxy_pass http://192.168.3.4:18600;
}
在上游Java服务器上可以观察到大量(约2000个)的TIME_WAIT状态的网络连接
从Nginx的error日志中还发现与Java服务器建立连接偶发失败的情况:
[error] 9208#0: *32907 connect() failed (111: Connection refused) while connecting to upstream, client: **.**.**.**, server: localhost, request: "POST /api HTTP/1.1", upstream: "http://**.**.**.**:18600/api", host: "**.**.**.**:8080"
2. 原因
由于QPS较高,Nginx与上游Java服务器建立的都是Http短连接,需要不停的创建和关闭TCP连接。而主动关闭TCP连接的一方需要等2MSL之后才会真正释放TCP连接,在2MSL之前连接的状态都是TIME_WAIT。
参考:TIME_WAIT状态产生的原因、过多的危害_爱吃芝麻球的博客-CSDN博客_time_wait连接过多的原因
由于每次上游Java服务在发送完响应报文后主动关闭了连接,所以作为主动关闭连接的一方,当并发量较高时就会产生大量的TIME_WAIT状态的连接。
3. 解决办法
解决的办法就是让Nginx与上游Java服务器之间通过Http 1.1的 Keepalive协议重用TCP连接,减少TCP连接数量
第一步: 修改location模块,添加http 1.1 协议头
location / {
proxy_pass http://192.168.3.4:18600;
# 添加http 1.1 协议头,这样上游Java服务就会启用keepalive,不会主动关闭TCP连接了
proxy_http_version 1.1;
proxy_set_header Connection "";
}
修改nginx配置重新生效后,发现上游Java服务器上的TIME_WAIT连接少了,但是Nginx服务器到上游Java服务器的TIME_WAIT连接却变多了。
原因在于Nginx自身没有复用到上游Java服务器的TCP连接,每次收到完整的响应报文之后就关闭连接了。
而这一次Nginx服务作为主动关闭TCP连接的一方,所以从Nginx服务器上TIME_WAIT的连接变多了。
第二步: 让Nginx主动重用TCP连接
Nginx的upstream模块中也有一个keepalive参数,但是这个参数与http协议中的keepalive参数的意义完全不同,upstream中的keepalive参数表示与上游服务建立的连接可以空闲的最大数量。
即如果在upstream模块中配置了keepalive参数,那么Nginx与上游服务之间建立的TCP连接就有了一个缓冲的池子,不再是用完立即释放了,而是可以有一个缓冲的池子可以放进去。keepalive参数的含义就是这个缓冲池子的最大值
参考:长连接 · Nginx 学习笔记 (gitbooks.io)
所以单独提取出一个upstream模块,并设置keepalive参数
upstream java_server {
server 192.168.3.4:18600;
# 设置可复用的tcp连接的空闲数量的最大值
keepalive 50;
}
location / {
proxy_pass http://java_server;
# 添加http 1.1 协议头,这样上游Java服务就会启用keepalive,不会主动关闭TCP连接了
proxy_http_version 1.1;
proxy_set_header Connection "";
}
经过以上两步操作之后,Nginx与上游服务器之间的连接数就降下来了,直接降到了20个左右。。。