服务器出现大量CLOSE_WAIT引发的进程假死,从而拒绝服务

2018-06-28  本文已影响102人  长腿小西瓜

现象

    进程在,curl请求没反应,判定为进程假死

分析

查看TCP连接

[root@ip-XXXXbackup]# netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'

TIME_WAIT 84
CLOSE_WAIT 8192
ESTABLISHED 141
SYN_RECV 1
LAST_ACK 1
`

dump问题线程的堆栈

➜ Commands pwd
/System/Library/Frameworks/JavaVM.framework/Versions/Current/Commands
➜ Commands ./jvisualvm

图一,导入dump文件 图二,查看线程数 图三,大量的RUNNABLE线程
RUNNABLE线程表示正在运行的线程

查看代码,发现这个接口,做的工作是去aliyun拿签名信息,然后返回。使用阿里云提供的sdk。

   <dependency>
       <groupId>com.aliyun</groupId>
       <artifactId>aliyun-java-sdk-core</artifactId>
       <version>3.2.6</version>
    </dependency>

查看日志中,这条请求的时间为5个小时前的。对比dump的时间,和RUNNABLE,说明这个请求的线程一直处于运行状态,没有终止。

继续看源代码,接口做的工作,就是用阿里云的SDK接口去拿签名信息,拿到后返回给客户端。理论上,阿里云的请求不可能这么慢,即使慢,应该也要超时才对。阿里云的SDK封装的httpclient,httpclient是可以设置超时时间的。下面是SDK的超时时间:

图四,设置了连接超时和读取超时

再看业务代码,并没有设置超时时间:

IClientProfile profile = DefaultProfile.getProfile(region, accessKeyId, accessKeySecret);
            DefaultAcsClient client = new DefaultAcsClient(profile);
            // 创建一个 AssumeRoleRequest 并设置请求参数
            final AssumeRoleRequest request = new AssumeRoleRequest();
            request.setVersion(version);
            request.setMethod(MethodType.POST);
            request.setProtocol(protocolType);
            request.setRoleArn(roleArn);
            request.setRoleSessionName(roleSessionName);
            request.setPolicy(policy);
            if(expireTime > 0L) {
                request.setDurationSeconds(expireTime);
            }
            final AssumeRoleResponse response = client.getAcsResponse(request);
            return response;

所以如果阿里云出现问题,可能导致请求一只挂起,线程一只处于运行状态。

那为什么挂起请求,会在服务器出现大量的CLOSE_WAIT, 那需要从TCP的四次挥手说明。

TCP四次挥手

为什么需要四次挥手

以下内容来自google:

由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。
这原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。
收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。
首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。

(1) TCP客户端发送一个FIN,用来关闭客户到服务器的[数据传送]

(2) 服务器收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。和SYN一样,一个FIN将占用一个序号。

(3) 服务器关闭客户端的连接,发送一个FIN给客户端。

(4) 客户端发回ACK[报文]确认,并将确认序号设置为收到序号加1。
图五,TCP四次挥手

随便发一张TCP三次握手

图六,TCP三次握手
大量CLOSE_WAIT的原因

客户端调用服务端的接口,读取超时时间为8s,8s后,如果客户端将断开连接也就是图五中,客户端发送的FIN。 服务端收到FIN后,变成CLOSE_WAIT。
因为服务端又发起了对阿里云的请求,送上面的业务代码得知。因为没有设置超时时间,导致服务端线程一直RUNABLE。
当客户端大量访问该接口时,就会出现大量CLOSE_WAIT。

为什么进程的CLOSE_WAIT数量为8192

出现假死时,CLOSE_WAIT数量为8192,表现的现象责任,应用服务器进程拒绝服务。

tomcat的最大连接数

tomcat的最大连接数参数是maxConnections,这个值表示最多可以有多少个socket连接到tomcat上。BIO模式下默认最大连接数是它的最大线程数(缺省是200),NIO模式下默认是10000,APR模式则是8192(windows上则是低于或等于maxConnections的1024的倍数)。如果设置为-1则表示不限制。

springboot升级到最新的2.0版本,默认开启的APR, 可以设置如下参数

server.tomcat.max-connections=
server.tomcat.accept-count=

解决方法

增加连接和读取超时时间
上一篇下一篇

猜你喜欢

热点阅读