分布式事务Lottor在测试环境中运行一段时间以后,出现Lottor客户端链接不上Lottor Server的状况。通过排查,发现根源问题是Lottor客户端获取不到Lottor Server的集群信息。html
Lottor Server启动了两个端口:9666为Tomcat容器的端口、9888为netty 服务器的端口。经过以下命令查看端口的状态:程序员
netstat -apn
复制代码
netstat -apn|grep 9888
复制代码
首先查看了netty服务器的端口,链接很正常,和咱们上面描述的问题没什么联系。排除netty服务链接的因素。apache
其次查看Tomcat容器的端口的状态:tomcat
netstat -apn|grep 9666
复制代码
发现有大量的FIN_WAIT2 和 CLOSE_WAIT状态的链接。 bash
另外还能够经过以下的命令查看当前的链接数:服务器
netstat -apn|grep 9666 | wc -l
复制代码
更加细化能够发现FIN_WAIT2 和 CLOSE_WAIT状态的链接数量至关。微信
通讯的客户端和服务端经过三次握手创建TCP链接。当数据传输完毕后,双方均可释放链接。最开始的时候,客户端和服务器都是处于ESTABLISHED状态,而后客户端主动关闭,服务器被动关闭。网络
链接的主动断开是能够发生在客户端,也一样能够发生在服务端。经常使用的三个状态是:ESTABLISHED 表示正在通讯,TIME_WAIT 表示主动关闭,CLOSE_WAIT 表示被动关闭。负载均衡
最开始的时候,客户端和服务器都是处于ESTABLISHED状态,而后客户端或服务端发起主动关闭,另外一端则是被动关闭。socket
当一方接受到来自应用断开链接的信号时候,就发送 FIN 数据报来进行主动断开,而且该链接进入 FIN_WAIT1 状态,链接处于半段开状态(能够接受、应答数据,当不能发送数据),并将链接的控制权托管给 Kernel,程序就再也不进行处理。通常状况下,链接处理 FIN_WAIT1 的状态只是持续很短的一段时间。
被动关闭的一端在收到FIN包文以后,其所处的状态为CLOSE_WAIT,表示被动关闭。
当主动断开一端的 FIN 请求发送出去后,而且成功够接受到相应的 ACK 请求后,就进入了 FIN_WAIT2 状态。其实 FIN_WAIT1 和 FIN_WAIT2 状态都是在等待对方的 FIN 数据报。当 TCP 一直保持这个状态的时候,对方就有可能永远都不断开链接,致使该链接一直保持着。
从以上TCP链接关闭的状态转换图能够看出,主动关闭的一方在发送完对对方FIN报文的确认(ACK)报文后,会进入TIME_WAIT状态。TIME_WAIT状态也称为2MSL状态。MSL值得是数据包在网络中的最大生存时间。产生这种结果使得这个TCP链接在2MSL链接等待期间,定义这个链接的四元组(客户端IP地址和端口,服务端IP地址和端口号)不能被使用。
在上一小节介绍了TCP四次挥手的相关状态以后,咱们将会分析在什么状况下,链接处于CLOSE_WAIT状态呢? 在被动关闭链接状况下,已经接收到FIN,可是尚未发送本身的FIN的时刻,链接处于CLOSE_WAIT状态。 一般来说,CLOSE_WAIT状态的持续时间应该很短,正如SYN_RCVD状态。可是在一些特殊状况下,就会出现链接长时间处于CLOSE_WAIT状态的状况。
出现大量close_wait的现象,主要缘由是某种状况下对方关闭了socket连接,可是另外一端因为正在读写,没有关闭链接。代码须要判断socket,一旦读到0,断开链接,read返回负,检查一下errno,若是不是AGAIN,就断开链接。
Linux分配给一个用户的文件句柄是有限的,而TIME_WAIT和CLOSE_WAIT两种状态若是一直被保持,那么意味着对应数目的通道就一直被占着,一旦达到句柄数上限,新的请求就没法被处理了,接着就是大量Too Many Open Files异常,致使tomcat崩溃。关于TIME_WAIT过多的解决方案参见TIME_WAIT数量太多。
从原理上来说,因为Server的Socket在客户端已经关闭时而没有调用关闭,形成服务器端的链接处在“挂起”状态,而客户端则处在等待应答的状态上。此问题的典型特征是:一端处于FIN_WAIT2 ,而另外一端处于CLOSE_WAIT。具体来讲:
1.代码层面上未对链接进行关闭,好比关闭代码未写在 finally 块关闭,若是程序中发生异常就会跳过关闭代码,天然未发出指令关闭,链接一直由程序托管,内核也无权处理,天然不会发出 FIN 请求,致使链接一直在 CLOSE_WAIT 。
2.程序响应过慢,好比双方进行通信,当客户端请求服务端迟迟得不到响应,就断开链接,从新发起请求,致使服务端一直忙于业务处理,没空去关闭链接。这种状况也会致使这个问题。
Lottor中,客户端定时刷新本地存储的Lottor Server的地址信息,具体来讲是每60秒刷新一次,经过HttpClient请求获取结果。笔者根据网上查找的资料和如上的分析,首先尝试将周期性刷新关闭,观察Lottor Server的9666端口的链接数。以前线性增加的链接数停了下来,初步定位到问题的根源。
服务器A是一台爬虫服务器,它使用简单的HttpClient去请求资源服务器B上面的apache获取文件资源,正常状况下,若是请求成功,那么在抓取完 资源后,服务器A会主动发出关闭链接的请求,这个时候就是主动关闭链接,服务器A的链接状态咱们能够看到是TIME_WAIT。若是一旦发生异常呢?假设 请求的资源服务器B上并不存在,那么这个时候就会由服务器B发出关闭链接的请求,服务器A就是被动的关闭了链接,若是服务器A被动关闭链接以后程序员忘了 让HttpClient释放链接,那就会形成CLOSE_WAIT的状态了。
笔者的场景如上面的状况通常,该问题的解决方法是咱们在遇到异常的时候应该直接调用停止本次链接,以防CLOSE_WAIT状态的持续。案例能够参见HttpClient链接池抛出大量ConnectionPoolTimeoutException: Timeout waiting for connection异常排查。笔者后来的改进方法是重写了这部分的逻辑,由Spring Cloud FeignClient调用执行,避免以前的负载均衡查询等繁琐的操做。