TIME_WAIT状态
一次无意中再群上看到有人讨论为什么循环connect socket发送信息,循环到一定程度就发送不了数据了,代码大致上时这样的
import socket
host = '127.0.0.1'
ip = 8888
while True:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((host, ip))
sock.sendall('hahahah')
sock.close()
然后我就说可能时太多的链接,导致同时很多端口都占用了,而且处于time_await状态,把端口全部耗尽,以至于后面的链接就链接不上了
TIME_WAIT状态是怎么产生的
time_await.jpg上面这个图片展示了TCP从连接建立到连接释放的过程中,客户端和服务端的状态变化图。如果只看连接释放阶段,四次握手
* 客户端先发送FIN,进入FIN_WAIT1状态
* 服务端收到FIN,发送ACK,进入CLOSE_WAIT状态,客户端收到这个ACK,进入FIN_WAIT2状态
* 服务端发送FIN,进入LAST_ACK状态
* 客户端收到FIN,发送ACK,进入TIME_WAIT状态,服务端收到ACK,进入CLOSE状态
* 客户端TIME_WAIT持续2倍MSL时长,在linux体系中大概是60s,转换成CLOSE状态
当然在这个例子和上面的图片中,使用客户端和服务端来描述是不准确的,TCP主动断开连接的一方可能是客户端,也可能是服务端。所以使用主动断开的一方,和被动断开的一方替换上面的图可能更为贴切。
不管怎么说,TIME_WAIT的状态就是主动断开的一方,发送完最后一次ACK之后进入的状态。并且持续时间还挺长的。
能不能发送完ACK之后不进入TIME_WAIT就直接进入CLOSE状态呢?不行的,这个是为了TCP协议的可靠性,由于网络原因,ACK可能会发送失败,那么这个时候,被动一方会主动重新发送一次FIN,这个时候如果主动方在TIME_WAIT状态,则还会再发送一次ACK,从而保证可靠性。那么从这个解释来说,2MSL的时长设定是可以理解的,MSL是报文最大生存时间,如果重新发送,一个FIN+一个ACK,再加上不定期的延迟时间,大致是在2MSL的范围。
所以从理论上说,网上调试参数降低TIME_WAIT的持续时间的方法是一种以可靠性换取性能的一种方式。嗯,质量守恒定理还是铁律。
服务端TIME_WAIT过多
# 通过命令列出各种socket状态个数
ss -ant|awk '{s[$1]++} END {for (k in s) print k, s[k] }'
# 返回
CLOSE-WAIT 3
ESTAB 143
LISTEN 64
SYN-SENT 2
State 1
对于上面的例子, 就是因为每次循环connect服务器端发送数据以后,都会立刻socket.close(), 而这个测试客户端和服务器端都在同一个机器上面,所以,就导致了服务器出现大量TIME_AWAIT, 端口被耗尽。
如果 HTTP服务并有依赖外部mysql或者redis等服务,
对于HTTP协议的请求来说
就是一个简单的Hello world,而TIME_WAIT的是主动断开方才会出现的,所以主动断开方是服务端?
答案是是的。在HTTP1.1协议中,有个 Connection 头,Connection有两个值,close和keep-alive,这个头就相当于客户端告诉服务端,服务端你执行完成请求之后,是关闭连接还是保持连接,保持连接就意味着在保持连接期间,只能由客户端主动断开连接。还有一个keep-alive的头,设置的值就代表了服务端保持连接保持多久。
HTTP默认的Connection值为close,那么就意味着关闭请求的一方几乎都会是由服务端这边发起的。那么这个服务端产生TIME_WAIT过多的情况就很正常了。
虽然HTTP默认Connection值为close,但是现在的浏览器发送请求的时候一般都会设置Connection为keep-alive了。所以,也有人说,现在没有必要通过调整参数来使TIME_WAIT降低了。
TIME_AWAIT过多危害
- 网络情况不好时,如果主动方无TIME_WAIT等待,关闭前个连接后,主动方与被动方又建立起新的TCP连接,这时被动方重传或延时过来的FIN包过来后会直接影响新的TCP连接;
- 同样网络情况不好并且无TIME_WAIT等待,关闭连接后无新连接,当接收到被动方重传或延迟的FIN包后,会给被动方回一个RST包,可能会影响被动方其它的服务连接。
- 过多的话会占用内存,一个time_await占用4k大小
解决方法
相关参数优化调整(当然得根据服务器的实际情况配置,这里着重讲参数意义):
既然知道了TIME_WAIT的用意了,尽量按照TCP的协议规定来调整,对于tw的reuse、recycle其实是违反TCP协议规定的,服务器资源允许、负载不大的条件下,尽量不要打开,当出现TCP: time wait bucket table overflow,尽量调大下面参数:
tcp_max_tw_buckets = 256000
调整次参数的同时,要调整TIME_WAIT_2到TIME_WAIT的超时时间,默认是60s,优化到30s:
net.ipv4.tcp_fin_timeout = 30
其它TCP本身的配合参数类似与synack重传次数、syn重传次数等以后介绍,优化后也是有所益处的。
下面再说一下linux里TIME_WAIT专有的优化参数reuse、recycle,默认也都是关闭的,这两个参数必须在timestamps打开的前提下才能生效使用:
net.ipv4.tcp_timestamps = 1
net.ipv4.tcp_tw_reuse = 1
机器作为客户端时起作用,开启后time_wait在一秒内回收
net.ipv4.tcp_tw_recycle = 0 (不要开启,现在互联网NAT结构很多,可能直接无法三次握手)
开启后在3.5*RTO(RTO时间是根据RTT时间计算而来)内回收TIME_WAIT,并60s内同一源ip主机的socket connect请求中的timestamp必须是递增的,对于服务端,同一个源ip可能会是NAT后很多机器,这些机器timestamp递增性无可保证,服务器会拒绝非递增请求连接,直接导致不能三次握手。
踩坑经验
一般情况下TIME_WAIT过多的错误, 是调整tcp_max_tw_buckets, 这个值默认很大, 把这个值调小, 我的机器32核16G, 设置的是8192. 如果net.ipv4.tcp_tw_recycle=1, 那么在为移动终端提供服务的时候, 服务器接收到的TCP包多数是乱序的, 比如服务器先收到了 tcp包为(201603290003), 后收到(201603290002), 也就是移动端先发的一个包后到达的服务器, 那么其中一个包很大概率会在短暂时间内被recycle, 后果可想而知.一般这种包的乱序是因为移动网络不稳定造成的. 一般有网线直连的PC和其他网线直连设备不出现此情况.
以上只是我的个人经验, 和在recycle上踩过的坑, 请楼主参考.
参考
http://benpaozhe.blog.51cto.com/10239098/1767612
https://huoding.com/2013/12/31/316
网友评论