某日线上登录出现故障,排查日志发现HttpClient请求时随机分配到的端口被占用,导致第三方登录拉取信息时无法拉取成功,错误如下:
java.net.BindException: Address already in use (Bind failed)
at java.net.PlainSocketImpl.socketBind(Native Method) ~[na:1.8.0_111]
at java.net.AbstractPlainSocketImpl.bind(AbstractPlainSocketImpl.java:387) ~[na:1.8.0_111]
at java.net.Socket.bind(Socket.java:644) ~[na:1.8.0_111]
at sun.reflect.GeneratedMethodAccessor2044.invoke(Unknown Source) ~[na:na]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_111]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_111]
at org.apache.commons.httpclient.protocol.ReflectionSocketFactory.createSocket(ReflectionSocketFactory.java:139) ~[commons-httpclient-3.1.jar:na]
at org.apache.commons.httpclient.protocol.DefaultProtocolSocketFactory.createSocket(DefaultProtocolSocketFactory.java:125) ~[commons-httpclient-3.1.jar:na]
at org.apache.commons.httpclient.HttpConnection.open(HttpConnection.java:707) ~[commons-httpclient-3.1.jar:na]
.....
这个问题很奇怪,linux端口分配会避免端口冲突的,然后检查服务器发现大量tcp连接处于CLOSE_WAIT
状态,不过对应的是另外一个项目.
统计信息如下(命令netstat -nat | awk 'FNR>2{print $NF}' | sort | uniq -c
),简直恐怖.
TCP关闭连接时四次挥手的过程,如下图所示(图来自网络):
有图可知,主动方发起关闭请求也就是FIN
包后,被动方接收到包,被动方接着进入CLOSE_WAIT
状态,接着被动方发送FIN
包告知主动方自己已关闭后进入LAST_ACK
状态.
那么当被动方这个FIN
包没有发送成功,那么其就一直处于CLOSE_WAIT
状态.那么问题成功转换为以下几个小问题:
CLOSE_WAIT
有什么危害?
CLOSE_WAIT
状态不会自己消失,除非对应的应用进程死掉,不会消失就意味着一直占用服务器资源,端口总数又只有65535,因此这里的服务器作为连接的发起者就会造成大量端口被占用,一旦占用完就导致后面的请求都发不出去,也就是一开始图上另一个项目发请求出现的Address already in use (Bind failed)
错误.FIN
包会发送失败?
知道了产生的原因,自然好解决,根据netstat
给出的信息包括pid定位到具体的应用,然后通过git查看最近代码改动,最终找到之前上线的一段代码使用了python的httplib
,使用完却没有主动close释放连接,因此出现了这个问题.
那么为什么HttpClient访问时端口会分配到CLOSE_WAIT对应的端口?
Linux会为每一次请求分配临时端口,这个分配范围在/proc/sys/net/ipv4/ip_local_port_range
中有记录,在我这台服务器上其值是20000-65535
,大量的CLOSE_WAIT
就会导致可分配的端口数减少,因此系统会在指定范围内选择一个没有冲突的端口,一旦端口消耗完毕就会造成冲突.也就是上面的错误Address already in use (Bind failed)
.
上面结果图中TIME_WAIT
也有几百个,这个是什么原因?
对于四次挥手过程中,当主动方接收到被动放的关闭确认信号FIN
后,主动方会回复一个ACK
信号,然后会进入TIME_WAIT
状态,此时会等待2MLS,在Linux中也就是60s,因此相对上述2000多个活跃tcp来说,这100多的TIME_WAIT
是正常现象.
然后为什么TCP主动方关闭后需要等待2MLS?
因为TCP是可靠的通信,在主动方回复ACK
时如果由于网络问题该包发送失败,那么被动方就会进行FIN
重传,此时重传会遇到两个场景:
因此超时等待机制是必要的,