TIME_WAIT发送在TCP连接中主动关闭连接的一方。
问题
TIME_WAIT消耗的是客户端的端口。
由于一个TCP连接是靠一个四元组唯一标识(客户端IP,客户端端口,服务器IP,服务器端口),在这个四元组中,服务器IP和服务器端口是固定的,而变量是客户端IP和客户端端口,所以当同一个客户端频繁向服务器建立连接时,其端口号是消耗很快的。建立次数越频繁就越有可能失败,因为内核给客户端进程绑定的有可能是同一个端口号,这样就没法建立连接,而
这个客户端和服务器处于TIME_WAIT的连接越多,客户端可选的端口就越少,这是TIME_WAIT的危害。
业务场景
Nginx 作为反向代理时,Nginx作为服务器的客户端。
大量的短链接,可能导致 Nginx 上的 TCP 连接处于 time_wait 状态:
每一个 time_wait 状态,都会占用一个「本地端口」,上限为 65535(16 bit,2 Byte);
当大量的连接处于 time_wait 时,新建立 TCP 连接会出错,address already in use : connect 异常
服务端的一个端口可以被很多客户端连接,但是每个连接都会占用一个句柄,理论上这个句柄打开的数量只受服务器资源的限制。
查看TIME_WAIT连接数
- netstat统计:各种连接的数量
$ netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
ESTABLISHED 1154
TIME_WAIT 1645
- ss
ss -s
Total: 3338 (kernel 32067)
TCP: 168 (estab 65, closed 12, orphaned 0, synrecv 0, timewait 1/0), ports 0
Transport Total IP IPv6
* 32067 - -
RAW 1 0 1
UDP 29 25 4
TCP 156 132 24
INET 186 157 29
FRAG 0 0 0
问题分析
大量的 TIME_WAIT 状态 TCP 连接存在,其本质原因是什么?
- 大量的短连接存在。
解决办法
客户端
HTTP 请求的头部,connection 设置为 keep-alive,保持存活一段时间:现在的浏览器,一般都这么进行了反向代理服务器
允许 time_wait 状态的 socket端口 被重用
必要时缩短time_wait时间
socket重用
因为有
TIME_WAIT
状态的存在,在重启程序的时候可能会出现java.net.BindException: Address already in use
的错误,这是因为这个端口TIME_WAIT
状态需要等待2MSL
。在RFC793
中规定MSL
的时间为2min,在实际使用中一般是30s或者1min,在高并发的情况下毫无疑问,这将造成大量连接无法建立的问题,那么有什么方法可以处理这些问题那?
SO_REUSEADDR
-
SO_REUSEADDR
设置为1在TIME_WAIT
时允许套接字端口复用。 -
SO_REUSEADDR
设置为0TIME_WAIT
时不允许允许套接字端口复用。
在Java中可以通过以下代码设置:
ServerSocket serverSocket = new ServerSocket();
// setReuseAddress 必须在 bind 函数调用之前执行
serverSocket.setReuseAddress(true);
SO_REUSEADDR
可以用在以下四种情况下。(摘自《Unix网络编程》卷一,即UNPv1)
- 当有一个有相同本地地址和端口的socket1处于TIME_WAIT状态时,而你启动的程序的socket2要占用该地址和端口,你的程序就要用到该选项。
-
SO_REUSEADDR
允许同一port上启动同一服务器的多个实例(多个进程)。但每个实例绑定的IP地址是不能相同的。在有多块网卡或用IP Alias技术的机器可 以测试这种情况。 -
SO_REUSEADDR
允许单个进程绑定相同的端口到多个socket
上,但每个socket
绑定的ip地址不同。这和2很相似,区别请看UNPv1
。 -
SO_REUSEADDR
允许完全相同的地址和端口的重复绑定。但这只用于UDP的多播,不用于TCP。
所以SO_REUSEADDR
并不是在所有情况下都可以使用的。
启动端口复用,不过TCP连接需要绑定不同的ip