网上有很多关于 time wait 的问题和修改方案,究竟什么是 time wait?作用是什么?会造成什么问题?如何解决?我们接下来一点一点看一下。
timewait 状态是 TCP 链接的主动关闭方会有的状态,在发出最后一个 ACK 包之后,主动关闭方进入 timewait 状态,以确保 ACK 包到达对端,以及等待网络中之前迷路的数据包完全消失,防止在端口被复用的时候收到迷路包从而出现收包错误。
上面在介绍 time wait 时已经说了,下面再详细说一下:
TCP协议在关闭连接的四次握手过程中,最终的ACK是由主动关闭连接的一端(后面统称A端)发出的,如果这个ACK丢失,对方(后面统称B端)将重发出最终的FIN,因此A端必须维护状态信息(TIME_WAIT)允许它重发最终的ACK。如果A端不维持TIME_WAIT状态,而是处于CLOSED 状态,那么A端将响应RST分节,B端收到后将此分节解释成一个错误(在java中会抛出connection reset的SocketException)。
因而,要实现TCP全双工连接的正常终止,必须处理终止过程中四个分节任何一个分节的丢失情况,主动关闭连接的A端必须维持TIME_WAIT状态 。
TCP分节可能由于路由器异常而“迷途”,在迷途期间,TCP发送端可能因确认超时而重发这个分节,迷途的分节在路由器修复后也会被送到最终目的地,这个迟到的迷途分节到达时可能会引起问题。在关闭“前一个连接”之后,马上又重新建立起一个相同的IP和端口之间的“新连接”,“前一个连接”的迷途重复分组在“前一个连接”终止后到达,而被“新连接”收到了。为了避免这个情况,TCP协议不允许处于TIME_WAIT状态的连接启动一个新的可用连接,因为TIME_WAIT状态持续2MSL,就可以保证当成功建立一个新TCP连接的时候,来自旧连接重复分组已经在网络中消逝。
time wait 的主要作用是保证关闭的TCP端口不立即被使用,来避免上面说的可能会发生的问题。也就是说,time wait 过多的话,可能造成端口无法使用。
TIME_Wait的主要作用是保证关闭的TCP端口不立即被使用。因为当网络存在延迟时,可能当某个端口被关闭后,网络中还有一些重传的TCP片在发向这个端口,如果这个端口立即建立新的TCP连接,则可能会有影响。所以使用2倍的MSL时间来限制这个端口立即被使用。这个等待时间基本是从1981年的RFC793过来的,对于服务器实在太长了些。而且每个TCP连接都各自有个数据结构,叫TCP Control block,Time_wait的时候这个数据结构没有被释放。所以当有太多的TCP连接时,内存可能会被占用很多。
如果有恶意程序大量的连接服务器,虽然很快断开,但产生大量的TIME_WAIT,逐渐消耗内存。虽然可以通过setsockopt(nListenSocket, SOL_SOCKET, SO_REUSEADDR, &nRet, n);让TIME_WAIT的本地地址和端口被新的进程使用,但系统给新进程分配的端口貌似只有一小部分落到了TIME_WAIT端口,还是无法解决TIME_WAIT带来的问题。
目前好像只能在TIME_WAIT的时间限制上做点文章了,不少管理员同志设置成30秒,至少能缓解服务器在高连接情况下的很大压力
1,使用长连接
使用长连接的话,会减少连接创建, 也就会减少 time wait 的产生。关于长连接,请参看:关于 Nginx 和 Tomcat 的 http 长连接设置。
2,设置 tcp_tw_reuse
tcp_tw_reuse:顾名思义就是复用TIME_WAIT连接。当创建新连接的时候,如果可能的话会考虑复用相应的TIME_WAIT连接。通常认为「tcp_tw_reuse」比「tcp_tw_recycle」安全一些,这是因为一来TIME_WAIT创建时间必须超过一秒才可能会被复用;二来只有连接的时间戳是递增的时候才会被复用。官方文档里是这样说的:如果从协议视角看它是安全的,那么就可以使用。这简直就是外交辞令啊!按我的看法,如果网络比较稳定,比如都是内网连接,那么就可以尝试使用。
不过需要注意的是在哪里使用,既然我们要复用连接,那么当然应该在连接的发起方使用,而不能在被连接方使用。举例来说:客户端向服务端发起HTTP请求,服务端响应后主动关闭连接,于是TIME_WAIT便留在了服务端,此类情况使用「tcp_tw_reuse」是无效的,因为服务端是被连接方,所以不存在复用连接一说。让我们延伸一点来看,比如说服务端是PHP,它查询另一个MySQL服务端,然后主动断开连接,于是TIME_WAIT就落在了PHP一侧,此类情况下使用「tcp_tw_reuse」是有效的,因为此时PHP相对于MySQL而言是客户端,它是连接的发起方,所以可以复用连接。
说明:如果使用tcp_tw_reuse,请激活tcp_timestamps,否则无效。
3,设置 tcp_max_tw_buckets
顾名思义就是控制TIME_WAIT总数。官网文档说这个选项只是为了阻止一些简单的DoS攻击,平常不要人为的降低它。如果缩小了它,那么系统会将多余的TIME_WAIT删除掉,日志里会显示:「TCP: time wait bucket table overflow」。
需要提醒大家的是物极必反,曾经看到有人把「tcp_max_tw_buckets」设置成0,也就是说完全抛弃TIME_WAIT,这就有些冒险了,用一句围棋谚语来说:入界宜缓。
4,不要设置 tcp_tw_recycle
顾名思义就是回收TIME_WAIT连接。可以说这个内核参数已经变成了大众处理TIME_WAIT的万金油,如果你在网络上搜索TIME_WAIT的解决方案,十有八九会推荐设置它,不过这里隐藏着一个不易察觉的陷阱
当多个客户端通过NAT方式联网并与服务端交互时,服务端看到的是同一个IP,也就是说对服务端而言这些客户端实际上等同于一个,可惜由于这些客户端的时间戳可能存在差异,于是乎从服务端的视角看,便可能出现时间戳错乱的现象,进而直接导致时间戳小的数据包被丢弃。参考:tcp_tw_recycle和tcp_timestamps导致connect失败问题。
有的地方说设置 tcp_fin_timeout。这个是在 fin_wait_2 状态的停留的时间,这个时间越短,到 time wait 状态的时间就越快。但这个是在连接关闭接收方
半天不返回 Fin 的基础上的。如果很快就返回 Fin 那么这个值没什么作用。
还有的地方意思是修改 tcp_fin_timeout,就是修改 time wait 的 MSL 时间,但其实不是这样的。MSL指的是报文段的最大生存时间,如果报文段在网络活动了MSL时间,还没有被接收,那么会被丢弃。关于MSL的大小,RFC 793协议中给出的建议是两分钟,不过实际上不同的操作系统可能有不同的设置,以Linux为例,通常是半分钟,两倍的MSL就是一分钟,也就是60秒,并且这个数值是硬编码在内核中的,也就是说除非你重新编译内核,否则没法修改它:
define TCP_TIMEWAIT_LEN (60*HZ)
对于客户端
1. 作为客户端因为有端口65535问题,TIME_OUT过多直接影响处理能力,打开tw_reuse 即可解决,不建议同时打开tw_recycle,帮助不大。
2. tw_reuse 帮助客户端1s完成连接回收,基本可实现单机6w/s请求,需要再高就增加IP数量吧。
3. 如果内网压测场景,且客户端不需要接收连接,同时tw_recycle 会有一点点好处。
4. 业务上也可以设计由服务端主动关闭连接
对于服务端
1. 打开tw_reuse无效
2. 线上环境 tw_recycle 不要打开
服务器处于NAT 负载后,或者客户端处于NAT后(这是一定的事情,基本公司家庭网络都走NAT);公网服务打开就可能造成部分连接失败,内网的话到时可以视情况打开;像我所在公司对外服务都放在负载后面,负载会把timestamp 都给清空,好吧,就算你打开也不起作用。
MSL 由来
发起连接关闭方回复最后一个fin 的ack,为避免对方ack 收不到、重发的或还在中间路由上的fin 把新连接给干掉了,等个2MSL,4min。也就是连接有谁关闭的那一方有time_wait问题,被关那方无此问题。
reuse、recycle
通过timestamp的递增性来区分是否新连接,新连接的timestamp更大,那么小的timestamp的fin 就不会fin掉新连接。
reuse
通过timestamp 递增性,客户端、服务器能够处理outofbind fin包
recycle
对于服务端,同一个src ip,可能会是NAT后很多机器,这些机器timestamp递增性无可保证,服务器会拒绝非递增请求连接。
TIME_WAIT状态详解:关于 time wait 的存在的作用,也是它要解决的问题。
TIME_WAIT状态在tcp连接中的含义与作用:也是关于 time wait 的存在的作用。这个讲的更多,挺仔细的,虽然有的地方讲的重复了。
理解TIME_WAIT:讲的非常好,讲了 time wait 原理,还有一些 time wait 多过的解决方法。
再叙TIME_WAIT:解决 time wait 方法总结的非常好,还有一些案例。
tcp_tw_reuse、tcp_tw_recycle 使用场景及注意事项:非常好的总结,从客户端和服务器端进行了总结。
TCP协议中的三次握手和四次挥手(图解):里面关于 4 次挥手的解释非常好。
TCP的三次握手、四次挥手–非常详细讲解:里面讲的状态迁移非常好。还讲解了每次挥手是关闭读还是写。
TCP/IP状态图的TIME_WAIT作用:4 次挥手的文字流程讲的很好