TCP之Time Wait漫谈

简单来说,TIME_WAIT存在的原因有两个:

  • 防止一个连接中延迟的数据段会被后序的连接错误的解析。
  • 为了实现TCP全双工连接的终止可靠性。

TIME_WAIT产生的过程与原因可以查看笔记:TCP有限状态机分析

TIME_WAIT状态的效果

TIME_WAIT状态只出现在主动关闭连接的一方,效果分草案版与具体实现版:

  • 草案定义的效果:一个插口对(即包含本地IP地址、本地端口、远端IP地址与远端端口的4元组)在它处于TIME_WAIT的状态下,不能被使用。

  • 大多实现版本的效果:允许一个进程重新使用仍处于2MSL等待的端口(需要使用setsockopt()函数设置SO_REUSEADDR)。同时,实现允许一个新的连接请求到达仍处于TIME_WAIT状态的连接,只要新的序号大于该连接前一个替身的最后序号。

总之,主动关闭请求的主机,在TIME_WAIT状态下,不能再使用原来的端口向原来的服务器的服务端口发起请求。即主动关闭的主机,要想在2MSL时间内再访问相同资源,必须使用其他的源端口。

TIME_WAIT可怕吗?

如果你通过 ss -tan state time-wait | wc -l 发现,系统中有很多TIME_WAIT,很多人都会紧张。多少算多呢?几百几千?如果是这个量级,其实真的没必要紧张。第一,这个量级,因为TIME_WAIT所占用的内存很少很少;因为记录和寻找可用的local port所消耗的CPU也基本可以忽略。

  1. 内核里有保存所有连接的一个hash table,这个hash table里面既包含TIME_WAIT状态的连接,也包含其他状态的连接。主要用于有新的数据到来的时候,从这个hash table里快速找到这条连接。不同的内核对这个hash table的大小设置不同,你可以通过dmesg命令去找到你的内核设置的大小:

  2. 还有一个hash table用来保存所有的bound ports,主要用于可以快速的找到一个可用的端口或者随机端口:

其实,如果你再进一步去研究,1万条TIME_WAIT的连接,也就多消耗1M左右的内存,对现代的很多服务器,已经不算什么了。至于CPU,能减少它当然更好,但是不至于因为1万多个hash item就担忧。

TIME_WAIT怎么调优?

当主机作为客户端是,开启net.ipv4.tcp_tw_reuse, net.ipv4.tcp_timestamps

net.ipv4.tcp_tw_recycle = 0 //关闭 net.ipv4.tcp_tw_reuse = 1  //开启net.ipv4.tcp_timestamps = 1  //开启

tcp_tw_recycle

tcp_tw_recycle:顾名思义就是回收TIME_WAIT连接。可以说这个内核参数已经变成了大众处理TIME_WAIT的万金油,如果你在网络上搜索TIME_WAIT的解决方案,十有八九会推荐设置它,不过这里隐藏着一个不易察觉的陷阱

当多个客户端通过NAT方式联网并与服务端交互时,服务端看到的是同一个IP,也就是说对服务端而言这些客户端实际上等同于一个,可惜由于这些客户端的时间戳可能存在差异,于是乎从服务端的视角看,便可能出现时间戳错乱的现象,进而直接导致时间戳小的数据包被丢弃(查看linux 2.6.32内核源码,发现tcp_tw_recycle/tcp_timestamps都开启的条件下,60s内同一源ip主机的socket connect请求中的timestamp必须是递增的)。参考:tcp_tw_recycle和tcp_timestamps导致connect失败问题

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,否则无效。

tcp_max_tw_buckets

tcp_max_tw_buckets:顾名思义就是控制TIME_WAIT总数。官网文档说这个选项只是为了阻止一些简单的DoS攻击,平常不要人为的降低它。如果缩小了它,那么系统会将多余的TIME_WAIT删除掉,日志里会显示:「TCP: time wait bucket table overflow」。

tcp_fin_timeout

缩短MSL的时间,如设置MSL的时间为30s.

net.ipv4.tcp_fin_timeout = 30

参考文献

Was this helpful?

0 / 0

发表回复 0