http://huoding.com/2013/12/31/316

之所以起这样一个题目是因为很久以前我曾经写过一篇介绍TIME_WAIT的文章,不过当时基本属于浅尝辄止,并没深入说明问题的来龙去脉,碰巧这段时间反复被别人问到相关的问题,让我觉得有必要全面总结一下,以备不时之需。

讨论前大家可以拿手头的服务器摸摸底,记住「ss」比「netstat」快:

shell> ss -ant | awk '
NR>1 {++s[$1]} END {for(k in s) print k,s[k]}
'

如果你只是想单独查询一下TIME_WAIT的数量,那么还可以更简单一些:

shell> cat /proc/net/sockstat

我猜你一定被巨大无比的TIME_WAIT网络连接总数吓到了!以我个人的经验,对于一台繁忙的Web服务器来说,如果主要以短连接为主,那么其 TIME_WAIT网络连接总数很可能会达到几万,甚至十几万。虽然一个TIME_WAIT网络连接耗费的资源无非就是一个端口、一点内存,但是架不住基 数大,所以这始终是一个需要面对的问题。

为什么会存在TIME_WAIT?

TCP在建立连接的时候需要握手,同理,在关闭连接的时候也需要握手。为了更直观的说明关闭连接时握手的过程,我们引用「The TCP/IP Guide」中的例子

TCP Close

因为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连接已经不存在了,系统此时只能返回RST包
  • 新的TCP连接被建立起来了,延迟包可能干扰新的连接

不管是哪种情况都会让TCP不再可靠,所以TIME_WAIT状态有存在的必要性。

如何控制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,尽可能不让服务端主动关闭连接,而让客户端主动关闭连接,如此一来问题便迎刃而解了。

参考文档:

  1. tcp短连接TIME_WAIT问题解决方法大全(1)——高屋建瓴
  2. tcp短连接TIME_WAIT问题解决方法大全(2)——SO_LINGER
  3. tcp短连接TIME_WAIT问题解决方法大全(3)——tcp_tw_recycle
  4. tcp短连接TIME_WAIT问题解决方法大全(4)——tcp_tw_reuse
  5. tcp短连接TIME_WAIT问题解决方法大全(5)——tcp_max_tw_buckets
  6. http://blog.csdn.net/dog250/article/details/13760985

[转] 再叙TIME_WAIT的更多相关文章

  1. 再叙TIME_WAIT

    之所以起这样一个题目是因为很久以前我曾经写过一篇介绍TIME_WAIT的文章,不过当时基本属于浅尝辄止,并没深入说明问题的来龙去脉,碰巧这段时间反复被别人问到相关的问题,让我觉得有必要全面总结一下,以 ...

  2. EntityFramework Core Raw Query再叙注意事项

    前言 最近一直比较忙没有太多时间去更新博客,接下来会一直持续发表相关内容博客,上一篇我们讲到了EF Core中的原始查询,这节我们再来叙述一下原始查询,本文是基于在项目当中用到时发现的问题. 话题 我 ...

  3. EntityFramework Core Raw Query再叙注意事项后续

    前言 话说通过EntityFramwork Core进行原始查询又出问题,且听我娓娓道来. EntityFramework Core Raw Query后续 当我们进行复杂查询时我们会通过原始查询来进 ...

  4. 再叙ASM

    上一篇文章,我们已体验到ASM的威力,那么结合上面的代码解释ASM是怎么执行的. ClassWriter clazzWriter = new ClassWriter(0); 首先看下官方文档对Clas ...

  5. 再叙Java反射

    Java中的反射 本文为反射的基础知识部分. 能够分析类能力的程序被称为反射(reflective). 反射机制允许程序在运行时取得任何一个已知名称的class的内部信息,容许程序在运行时加载.探知. ...

  6. Nginx做前端Proxy时TIME_WAIT过多的问题

    我们的DSP系统目前基本非凌晨时段的QPS都在10W以上,我们使用Golang来处理这些HTTP请求,Web服务器的前端用Nginx来做负载均衡,通过Nginx的proxy_pass来与Golang交 ...

  7. 也说说TIME_WAIT状态

    也说说TIME_WAIT状态 一个朋友问到,自己用go写了一个简单的HTTP服务端程序,为什么压测的时候服务端会出现一段时间的TIME_WAIT超高的情况,导致压测的效果不好呢? 记得老王有两篇文章专 ...

  8. TCP的定时器

    TCP的定时器 在TCP协议中有的时候需要定期或者按照某个算法对某个事件进行触发,那么这个时候,TCP协议是使用定时器进行实现的.在TCP中,会有四种定时器: 重传定时器 坚持定时器 保活定时器 2M ...

  9. 一些有价值的Blog推荐

    待看的一些文章 1. 性能调优攻略 http://coolshell.cn/articles/7490.html 2. 内存的存储管理--段式和页式管理的区别 http://blog.sina.com ...

随机推荐

  1. 各浏览器Cookie大小、个数限制

    一.浏览器允许每个域名所包含的cookie数: Microsoft指出InternetExplorer8增加cookie限制为每个域名50个,但IE7似乎也允许每个域名50个cookie. Firef ...

  2. iOS:NAV+TABLE结合

    功能:点击列表项,用列表字符串作为参数创建一个新视图,新视图默认可以有一个BACK按钮回到上一个视图 // // main.m // Hello // // Created by lishujun o ...

  3. 抓取天涯文章的蜘蛛代码,刚经过更新(因为天涯页面HTML代码变化)

    #_*_coding:utf-8-*- import urllib2 import traceback import codecs from BeautifulSoup import Beautifu ...

  4. PyCharm 5.0.3 快捷键

    下面记录PyCharm 5.0.3常用快捷键 注释 注释行:Ctrl+/

  5. [BZOJ 3564] [SHOI2014] 信号增幅仪 【最小圆覆盖】

    题目链接:BZOJ - 3564 题目分析 求最小椭圆覆盖,题目给定了椭圆的长轴与 x 轴正方向的夹角,给定了椭圆长轴与短轴的比值. 那么先将所有点旋转一个角度,使椭圆长轴与 x 轴平行,再将所有点的 ...

  6. iOS9 App Thinning(应用瘦身)功能介绍

    iOS9 发布后,产生了一个使 App Thinning 无法正常运行的 bug.在iOS9.0.2 版本中,这个 bug 已经被修复,App Thinning 已经可以正常使用.当你从应用商店(Ap ...

  7. Python3.x和Python2.x的区别-转

    这个星期开始学习Python了,因为看的书都是基于Python2.x,而且我安装的是Python3.1,所以书上写的地方好多都不适用于Python3.1,特意在Google上search了一下3.x和 ...

  8. 最好的JAVA IDE IntelliJ IDEA使用简介(一)—之界面元素

    打开IDEA,(当第一次打开的时候出现的是一个欢迎页面,随便创建一个project来进入到IDEA的主界面),主界面显示如下: 主界面由6个主要区域组成(图中红色数字标注的) 1.菜单和工具栏 2.导 ...

  9. 【HDOJ】2830 Matrix Swapping II

    简单DP. /* 2830 */ #include <iostream> #include <string> #include <map> #include < ...

  10. JavaScript 设计风格&模式 概览 20140418

    基本的概念 在JavaScript中,一旦定义好一个变量,该变量会自动成为内置对象的一个属性,(如果该变量是全局变量,那么会成为全局对象的一个属性). 定义的变量实际上也是一个伪类,拥有自身的属性,该 ...