前段时间在项目维护当中遇见一个问题,方便以后再次遇见类似的问题,可以参考解决问题的思路,记录如下:
问题概述:
维护项目当中,客户这边发来消息,称自己的网站登录不上去了,提示用户名密码错误,我就登录到服务器上查看系统日志和报错情况,如图:
image.png这个异常来源部署的一个jar包,用于读取数据存入mongodb,再看了Tomcat也报错了,错误是:java.net.SocketException。看完这样的问题后,没有确切的解决方案,自己一脸懵逼无奈的重新启动服务,可以正常登陆。没有什么问题是重启解决不了的。
问题分析:
虽然重启服务能暂时以最快的速度解决问题,但是问题很容易复现,很明显,这个并不是根本的解决方案。
需要进行进一步的分析:
总结下来根据我们当时的代码和部署的系统有可能出现的原因:
1:因为首先看到的是mongodb的问题,所以猜测是mongodb链接数不够,而需要等待的链接数过多,导致资源无法尽快释放。
2:因为代码内部使用有很多的 HttpClient请求,调用另一个数据库的数据,可能是请求量过多,或者是请求未正常的关闭。导致内部资源泄露
3:系统并发量太大,链接数过多,部分系统或者非系统请求无法正常的释放关闭,而又持续请求,导致socket链接不断进行积压,从而导致系统崩溃。
根据以上分析,我们对系统进行了内部的测试。让错误重现。
image.png image.pngimage.png
通过cmd输入netstat -an 发现有大量处于TIME_WAIT状态的TCP链接,也就是那些Socket未释放的链接
image.png那么TIME_WAIT状态的来由是什么呢?
TCP链接需要三次握手,四次挥手。可以参考下面流程图:
三次握手建立连接示意图.png 四次握手关闭连接示意图.png从上面的三次握手建立连接示意图中可以知道,只要client端和server端都接收到了对方发送的ACK应答之后,双方就可以建立连接,之后就可以进行数据交互了,这个过程需要三步。
而四次握手关闭连接示意图中,TCP协议中,关闭TCP连接的是Server端(当然,关闭都可以由任意一方发起),当Server端发起关闭连接请求时,向Client端发送一个FIN报文,Client端收到FIN报文时,很可能还有数据需要发送,所以并不会立即关闭SOCKET,所以先回复一个ACK报文,告诉Server端,“你发的FIN报文我收到了”。当Client端的所有报文都发送完毕之后,Client端向Server端发送一个FIN报文,此时Client端进入关闭状态,不在发送数据。
Server端收到FIN报文后,就知道可以关闭连接了,但是网络是不可靠的,Client端并不知道Server端要关闭,所以Server端发送ACK后进入TIME_WAIT状态,如果Client端没有收到ACK则Server段可以重新发送。Client端收到ACK后,就知道可以断开连接了。Server端等待了2MSL(Max Segment Lifetime,最大报文生存时间)后依然没有收到回复,则证明Client端已正常断开,此时,Server端也可以断开连接了。2MSL的TIME_WAIT等待时间就是由此而来。
我们知道了TIME_WAIT的由来,TIME_WAIT 状态最大保持时间是2 * MSL,在1-4分钟之间,所以当系统并发过大,Client-Server连接数过多,Server端会在1-4分钟之内积累大量处于TIME_WAIT状态的无法释放的socket连接,导致服务器效率急剧下降,甚至耗完服务器的所有资源,最终导致No buffer space available (maximum connections reached?): connect
问题的发生
最终解决办法:
1:关闭不需要的链接:
检查在代码当中是否有请求完的链接没有正常关闭,如:HttpClient请求中链接是否正常关闭,
Mongodb读取数据时链接是否正常关闭,及时关闭请求链接和clean,使用
try{}catch(){}finally{}
释放请求,及时回收资源,避免内存溢出。
2:通过修改注册表进行配置,减少等待时间
通过regedit启动注册表编译器找到如下路径:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters
添加参数:
(1)新建
值名称:MaxUserPort
值类型:DWORD
值数据:65534(十六进制是FFFE)
有效范围:5000 - 65534 (十进制)
默认:0x1388 5000(十进制)
(2)新建
值名称:TCPTimedWaitDelay
值类型:DWORD
值数据:0000001e(30)
image.png
网友评论