之因此起这样一个题目是由于好久之前我曾经写过一篇介绍TIME_WAIT的文章,不过当时基本属于浅尝辄止,并没深刻说明问题的前因后果,碰巧这段时间反复被别人问到相关的问题,让我以为有必要全面总结一下,以备不时之需。 html
讨论前你们能够拿手头的服务器摸摸底,记住「ss」比「netstat」快: linux
shell> ss -ant | awk ' NR>1 {++s[$1]} END {for(k in s) print k,s[k]} '
若是你只是想单独查询一下TIME_WAIT的数量,那么还能够更简单一些: android
shell> cat /proc/net/sockstat
我猜你必定被巨大无比的TIME_WAIT网络链接总数吓到了!以我我的的经验,对于一台繁忙的Web服务器来讲,若是主要以短链接为主,那么其 TIME_WAIT网络链接总数极可能会达到几万,甚至十几万。虽然一个TIME_WAIT网络链接耗费的资源无非就是一个端口、一点内存,可是架不住基 数大,因此这始终是一个须要面对的问题。 git
TCP在创建链接的时候须要握手,同理,在关闭链接的时候也须要握手。为了更直观的说明关闭链接时握手的过程,咱们引用「The TCP/IP Guide」中的例子: github
由于TCP链接是双向的,因此在关闭链接的时候,两个方向各自都须要关闭。先发FIN包的一方执行的是主动关闭;后发FIN包的一方执行的是被动关闭。主动关闭的一方会进入TIME_WAIT状态,而且在此状态停留两倍的MSL时长。 安全
穿插一点MSL的知识:MSL指的是报文段的最大生存时间,若是报文段在网络活动了MSL时间,尚未被接收,那么会被丢弃。关于MSL的大小,RFC 793协议中给出的建议是两分钟,不过实际上不一样的操做系统可能有不一样的设置,以Linux为例,一般是半分钟,两倍的MSL就是一分钟,也就是60秒,而且这个数值是硬编码在内核中的,也就是说除非你从新编译内核,不然无法修改它: 服务器
#define TCP_TIMEWAIT_LEN (60*HZ)
若是每秒的链接数是一千的话,那么一分钟就可能会产生六万个TIME_WAIT。 网络
为何主动关闭的一方不直接进入CLOSED状态,而是进入TIME_WAIT状态,而且停留两倍的MSL时长呢?这是由于TCP是创建在不可靠网 络上的可靠的协议。例子:主动关闭的一方收到被动关闭的一方发出的FIN包后,回应ACK包,同时进入TIME_WAIT状态,可是由于网络缘由,主动关 闭的一方发送的这个ACK包极可能延迟,从而触发被动链接一方重传FIN包。极端状况下,这一去一回,就是两倍的MSL时长。若是主动关闭的一方跳过 TIME_WAIT直接进入CLOSED,或者在TIME_WAIT停留的时长不足两倍的MSL,那么当被动关闭的一方早先发出的延迟包到达后,就可能出 现相似下面的问题: tcp
不论是哪一种状况都会让TCP再也不可靠,因此TIME_WAIT状态有存在的必要性。
从前面的描述咱们能够得出这样的结论:TIME_WAIT这东西没有的话不行,不过太多可能也是个麻烦事。下面让咱们看看有哪些方法能够控制TIME_WAIT数量,这里只说一些常规方法,另一些诸如SO_LINGER之类的方法太过偏门,略过不谈。
ip_conntrack:顾名思义就是跟踪链接。一旦激活了此模块,就能在系统参数里发现不少用来控制网络链接状态超时的设置,其中天然也包括TIME_WAIT:
shell> modprobe ip_conntrack shell> sysctl net.ipv4.netfilter.ip_conntrack_tcp_timeout_time_wait
咱们能够尝试缩小它的设置,好比十秒,甚至一秒,具体设置成多少合适取决于网络状况而定,固然也能够参考相关的案例。不过就个人我的意见来讲,ip_conntrack引入的问题比解决的还多,好比性能会大幅降低,因此不建议使用。
tcp_tw_recycle:顾名思义就是回收TIME_WAIT链接。能够说这个内核参数已经变成了大众处理TIME_WAIT的万金油,若是你在网络上搜索TIME_WAIT的解决方案,十有八九会推荐设置它,不过这里隐藏着一个不易察觉的陷阱:
当多个客户端经过NAT方式联网并与服务端交互时,服务端看到的是同一个IP,也就是说对服务端而言这些客户端实际上等同于一个,惋惜因为这些客户端的时间戳可能存在差别,因而乎从服务端的视角看,即可能出现时间戳错乱的现象,进而直接致使时间戳小的数据包被丢弃。参考: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:顾名思义就是控制TIME_WAIT总数。官网文档说这个选项只是为了阻止一些简单的DoS攻击,日常不要人为的下降它。若是缩小了它,那么系统会将多余的TIME_WAIT删除掉,日志里会显示:「TCP: time wait bucket table overflow」。
须要提醒你们的是物极必反,曾经看到有人把「tcp_max_tw_buckets」设置成0,也就是说彻底抛弃TIME_WAIT,这就有些冒险了,用一句围棋谚语来讲:入界宜缓。
…
有时候,若是咱们换个角度去看问题,每每能获得四两拨千斤的效果。前面提到的例子:客户端向服务端发起HTTP请求,服务端响应后主动关闭链接,于 是TIME_WAIT便留在了服务端。这里的关键在于主动关闭链接的是服务端!在关闭TCP链接的时候,先出手的一方注定逃不开TIME_WAIT的宿 命,套用一句歌词:把个人悲伤留给本身,你的美丽让你带走。若是客户端可控的话,那么在服务端打开KeepAlive,尽量不让服务端主动关闭链接,而让客户端主动关闭链接,如此一来问题便迎刃而解了。
参考文档: