暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

服务器出现了大量CLOSE_WAIT怎么办

技术万花筒 2020-09-14
1755

    现在微服务大行其道,我们的应用服务越来越趋近于IO密集型调用,都是各种系统间调用来调用去,服务和服务间通过TCP/IP协议紧紧的耦合在一起。

    服务间出现了大量的CLOSE_WAIIT是怎么回事呢?这种情况有点复杂也有点棘手,如下图:   

这是怎么回事呢?要从传输层协议TCP的4次挥手说起:【参考计算机网络第五版-谢希仁著-第5.9节,TCP的连接管理】

客户机A和服务器B通信,如果客户机A在某个场景下需要端开和B建立的连接,断开的原因有很多,比如最常见的socket read timeout,需要经历以下几种状态机的转变:

  1. A进程主动断开连接,发起FIN的数据包,序列号为u,A立刻进入FIN-WAIT-1状态;

  2. B收到后,TCP的协议层立刻回复ACK,序列号为u+1,然后立即进入CLOSE_WAIT状态;同时,通知7层的应用进程,当前连接需要关闭。有两种情形:

    1. 如果B协议层得到来自于应用进程的确认,那么B的协议层会再发送一个ACK给A,A进入FIN-WAIT-2状态;

    2. 如果B上的应用进程继续发送数据,A还可以接收。此时连接处于半关闭状态,A不会发送数据,但是可以接收来自于B的数据。而B一直处于CLOSE_WAIT状态。

  3. 如果B上的应用进程确认可以关闭连接,那么最后再经过一次挥手,双方的连接最终会关闭。


可见,如果是某端的连接一直长时间处于CLOSE_WAIT状态,那么即为该端上的7层的应用进程不同意关闭连接,导致协议层迟迟无法第二次ACK对方的FIN包。导致连接迟迟无法关闭,因此造成连接的泄露。

    可以做个试验:

  1. 我们启动一个nginx在80端口上监听,其中keep_alive_timeout设置时间是30s;

  2. 我们通过java发起一个socket和nginx的80端口建立连接,同时写入一个标准达到http协议报文,然后保持该socket长时间不被释放,同时不设置socket读超时的时间。

  3. 观察java侧的socket状态


以下是连接ng的请求代码:

public static void main(String[] args) throws InterruptedException {


ExecutorService p = Executors.newFixedThreadPool(100);
List sockets= new ArrayList<>();
IntStream.range(1, 2).forEach(i -> {
p.submit(() -> {
try {
              Socket ss = new Socket("192.168.1.202", 80);
InputStream in=ss.getInputStream();
OutputStream outputStream = ss.getOutputStream();
outputStream.write(content().getBytes());
outputStream.flush();
log.info("{} send ok!",i);
InputStream is=ss.getInputStream();
BufferedInputStream streamReader = new BufferedInputStream(is);
BufferedReader bufferedReader= new BufferedReader(new InputStreamReader(streamReader, "utf-8"));
String line = null;
while((line = bufferedReader.readLine())!= null)
{
System.out.println("[response is:]"+line);
}
sockets.add(ss);
Thread.sleep(120*1000);
} catch (Exception e) {
e.printStackTrace();
}
}
);
});
Thread.sleep(150 * 1000);
}


其中content方法构造了一个标准的html报文:

public static String content(){
StringBuffer sb = new StringBuffer("GET /user?time=1 HTTP/1.1\r\n");
sb.append("Host: www.test.com\r\n");
sb.append("User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:53.0) Gecko/20100101 Firefox/53.0\r\n");
sb.append("Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n");
sb.append("Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3");
sb.append("Accept-Encoding: \r\n");
sb.append("Connection: keep-alive\r\n");
sb.append("Upgrade-Insecure-Requests: 1\r\n");
sb.append("\r\n");
System.out.println(sb.toString());
return sb.toString();
}

通过该脚本打印连接nginx的本地socket状态:

while true ;do netstat -an|grep 192.168.1.222.80|grep -v sshd;sleep 1;echo $(date +%H:%M:%S);done;

可见本地到nginx80的socket状态如下:

开始双方建连并开始通信,连接处于保持中,经过大概65s,连接发生了变化:

52057端口所在的客户端socket进入CLOSE_WAIT,时间在65s左右,此时,即为客户端一直持有该socket,且没有任何数据发送,nginx在等待了65s后强制将该socket断开,但是客户端一直持有该socket,于是就保持在了CLOSE_WAIT状态。分析这里面的两个因素:

1、超时

2、某端socket对象不予释放(当协议层通知需要关闭连接时,应用程序不予响应)

因此触发了socket的泄露,这个socket只能被超时释放,由该参数控制:

/proc/sys/net/ipv4/tcp_keepalive_time #default is 7200s

会存在很长的一段时间,约为2小时。

文章转载自技术万花筒,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论