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

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

作者: 长腿小西瓜 | 来源:发表于2018-06-28 08:47 被阅读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=
    

    解决方法

    增加连接和读取超时时间

    相关文章

      网友评论

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

          本文链接:https://www.haomeiwen.com/subject/pmdbyftx.html