服务器产生大量的TIME_WAIT或者CLOSE_WAIT的原因以及如何解决

大量的TIME_WAIT

netstat

ss -s

netstat -n | awk '/^tcp/ {++state[$NF]} END {for(key in state) print key,"\t",state[key]}'

RFC 793协议中给出的建议是两分钟,不过实际上不同的操作系统可能有不同的设置,以Linux为例,通常是半分钟,两倍的MSL就是一分钟,也就是60秒
MSL是Maximum Segment Lifetime英文的缩写,中文可以译为“报文最大生存时间”

TIME_WAIT状态存在的必要性。虽然双方都同意关闭连接了,而且握手的4个报文也都协调和发送完毕,按理可以直接回到CLOSED状态(就好比从SYN_SEND状态到ESTABLISH状态那样);但是因为我们必须要假想网络是不可靠的,你无法保证你最后发送的ACK报文会一定被对方收到,比如丢包或者延迟到达,因此对方处于LAST_ACK状态下的SOCKET可能会因为超时未收到ACK报文,而重发FIN报文,所以这个TIME_WAIT状态的作用就是用来重发可能丢失的ACK报文,并保证于此。

简单说timewait之所以等待2MSL的时长,是为了避免因为网络丢包或者网络延迟而造成的tcp传输不可靠,而这个time_wait状态则可以最大限度的提升网络传输的可靠性。

TIME_WAIT状态过多的危害

  1. 在socket的TIME_WAIT状态结束之前,该socket所占用的本地端口号将一直无法释放。
  2. 在高并发(每秒几万qps)并且采用短连接方式进行交互的系统中运行一段时间后,系统中就会存在大量的time_wait状态,如果time_wait状态把系统所有可用端口都占完了且尚未被系统回收时,就会出现无法向服务端创建新的socket连接的情况。此时系统几乎停转,任何链接都不能建立。
  3. 大量的time_wait状态也会系统一定的fd,内存和cpu资源,当然这个量一般比较小,并不是主要危害

如何优化TIME_WAIT过多的问题

方式一:调整系统内核参数


net.ipv4.tcp_tw_reuse = 1 表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;
net.ipv4.tcp_tw_recycle = 1 表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。

优化完内核参数后,可以执行sysctl -p命令,来激活上面的设置永久生效

方式二:调整短链接为长链接

短连接和长连接工作方式的区别:

短连接
连接->传输数据->关闭连接
HTTP是无状态的,浏览器和服务器每进行一次HTTP操作,就建立一次连接,但任务结束就中断连接。
也可以这样说:短连接是指SOCKET连接后发送后接收完数据后马上断开连接。

长连接
连接->传输数据->保持连接 -> 传输数据-> 。。。->关闭连接。
长连接指建立SOCKET连接后不管是否使用都保持连接,但安全性较差。

从区别上可以看出,长连接比短连接从根本上减少了关闭连接的次数,减少了TIME_WAIT状态的产生数量,在高并发的系统中,这种方式的改动非常有效果,可以明显减少系统TIME_WAIT的数量。

当使用nginx作为反向代理时,为了支持长连接,需要做到两点:

从client到nginx的连接是长连接
从nginx到server的连接是长连接

保持和client端的长连接

默认情况下,nginx已经自动开启了对client连接的keep alive支持(同时client发送的HTTP请求要求keep alive)。一般场景可以直接使用,但是对于一些比较特殊的场景,还是有必要调整个别参数(keepalive_timeout和keepalive_requests)。

http {
    keepalive_timeout  120s 120s;
    keepalive_requests 10000;
}
  • keepalive_timeout: 第一个参数:设置keep-alive客户端连接在服务器端保持开启的超时值(默认75s);值为0会禁用keep-alive客户端连接;
  • 第二个参数:可选、在响应的header域中设置一个值“Keep-Alive: timeout=time”;通常可以不用设置;

保持和server的长连接

为了让nginx和后端server(nginx称为upstream)之间保持长连接,典型设置如下:(默认nginx访问后端都是用的短连接(HTTP1.0),一个请求来了,Nginx 新开一个端口和后端建立连接,后端执行完毕后主动关闭该链接)Nginx 1.1以上版本的upstream已经支持keep-alive的,所以我们可以开启Nginx proxy的keep-alive来减少tcp连接:

upstream http_backend {
 server 127.0.0.1:8080;

 keepalive 1000;//设置nginx到upstream服务器的空闲keepalive连接的最大数量
}

server {
 ...

location /http/ {
 proxy_pass http://http_backend;
 proxy_http_version 1.1;//开启长链接
 proxy_set_header Connection "";
 ...
 }
}

总结

netstat -ant

ss -ant

本文主要介绍了在高并发场景中系统存在大量的TIME_WAIT的问题和优化策略,通过调整内核参数+nginx长链接优化方式,最终将几万的TIME_WAIT状态给下降的到几千,效果还是非常明显的,如果有遇到类似的情况,可以参考下本文的解决方式。

大量的CLOSE_WAIT

close_wait是被动关闭连接是形成的,根据TCP状态机,服务器端收到客户端发送的FIN,TCP协议栈会自动发送ACK,链接进入close_wait状态。但如果服务器端不执行socket的close()操作,状态就不能由close_wait迁移到last_ack,则系统中会存在很多close_wait状态的连接,如下图所示:
在这里插入图片描述
可能的原因如下:

  1. 关闭socket不及时:例如I/O线程被意外阻塞,或者I/O线程执行的用户自定义Task比例过高,导致I/O操作处理不及时,链路不能被及时释放。

通常,CLOSE_WAIT 状态在服务器停留时间很短,如果你发现大量的 CLOSE_WAIT 状态,那么就意味着被动关闭的一方没有及时发出 FIN 包,一般有如下几种可能:

  1. 程序问题:如果代码层面忘记了 close 相应的 socket 连接,那么自然不会发出 FIN 包,从而导致 CLOSE_WAIT 累积;或者代码不严谨,出现死循环之类的问题,导致即便后面写了 close 也永远执行不到。
  2. 响应太慢或者超时设置过小:如果连接双方不和谐,一方不耐烦直接 timeout,另一方却还在忙于耗时逻辑,就会导致 close 被延后。响应太慢是首要问题,不过换个角度看,也可能是 timeout 设置过小。

参考:
https://cloud.tencent.com/developer/article/1589962
https://mp.weixin.qq.com/s?__biz=MzI4MjA4ODU0Ng==&mid=402163560&idx=1&sn=5269044286ce1d142cca1b5fed3efab1&3rd=MzA3MDU4NTYzMw==&scene=6#rd
https://blog.csdn.net/yangguosb/article/details/79095255
https://blog.huoding.com/2016/01/19/488

你可能感兴趣的:(网络)