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

    http://blog.csdn.net/lxnkobe/article/details/7525317

    http://kerry.blog.51cto.com/172631/105233/

讨论前大家可以拿手头的服务器摸摸底,记住「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网络连接耗费的资源无非就是一个端口、一点内存,但是架不住基数大,所以这始终是一个需要面对的问题。(close_wait状态就是对端所处的状态。比如客户端是time_wait,服务端就是close_wait状态。)

TCP终止连接一般是需要交换四个分节。具体来看:

1、 应用进程(active close)首先调用close,于是导致TCP发送一个FIN分节,表示数据已分送完毕,请求关闭套接字。 
2、 另一端应用进程(passive close)接受收到FIN,并由该端的TCP确认(确认的过程是TCP发送ACK分节给对端套接字)。FIN的接受也作为文件结束符传递给上层应用进程。这里的文件结束符并非应用进程的EOF,在TCP字节流中,EOF的读或写通过收发一个特殊的FIN分节来实现。 
3、 另端(passive close)应用进程在接受到文件束符后,会调用close关闭它的套接字,这导致该端的TCP也发送了一个FIN分节。 
4、 主动关闭端(active close)接受到这个FIN后,TCP对它进行确认。(TCP发送ACK分节,值得注意的是主动关闭端在未接受到FIN之前,它的状态就是TIME_WAIT)。

这张图在google image中,花了五六分钟才找到,觉得这张图是最直观、易懂的。

TIME_OUT状态的存在的意义 
从图中,很清晰的看到TIME_WAIT状态发生在了active close 端,产生的时间点是发送ACK K+1 分节之后,原因是防止ACK分节在网络中丢失(lost),此时passive close进入LAST_ACK状态,意为等待ACK分节,如果此时ACK分节真的丢失了(passive close端的LAST_ACK超时),那么passive close端将会再次发送一个FIN K分节给对端。这就是为什么在图中,出现两次FIN的分节。

TIME_OUT存在的理由用术语来描述,摘自UNIX Network Programming Vol1 中: 
1、 可靠地实现TCP全双工连接的终止。 
2、 允许老的重复分节在网络中消逝。

TIME_OUT状态的持续时间 
图中标明了TIME_OUT状态的持续时间是最长分节生命周期(MSL)的两倍,即2MSL。RFC中的建议值是2分钟,Berkeley的实现传统上使用的是30秒,那么这意味着TIME_WAIT状态的延迟是在1~4分钟之间。TIME_WAIT是TCP协议用以保证被重新分配的socket不会受到之前残留的延迟重发报文影响的机制,是必要的逻辑保证。

(My:Linux的也是30s,编译在系统内核。要改的话,除非重新编译系统内核。)

既然TIME_OUT状态的存在是有其意义的,为什么这么多人对其如此敏感,对于CS的模式,大多是由客户机主动关闭连接,这也避免了TIME_OUT产生于服务端,但对于某些协议,如HTTP则是由服务器执行主动关闭的。(My:一般来说,尽量避免服务端过多的time_wait状态。因为这会导致socket可用的端口越来越少,影响系统的运行。【特别是短连接特别多的】系统。可以试着让系统更多的保持keepalive状态,可能会好点。)

解决方法:

【1】.如果可以的话,减小time_wait的时间。

  

vi /etc/sysctl.conf
编辑文件,加入以下内容:
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_fin_timeout = 30
 
然后执行 /sbin/sysctl -p 让参数生效。
 
net.ipv4.tcp_syncookies = 1 表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭;
net.ipv4.tcp_tw_reuse = 1 表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;
net.ipv4.tcp_tw_recycle = 1 表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。
net.ipv4.tcp_fin_timeout 修改系統默认的 TIMEOUT 时间。
 
但上面只是暂时的解决方案,还需要自己去找具体的错误。
 
 
【2】.TCP的SO_LINGER 选项 
相信只要提到TIME_OUT,SO_LINGER就会现身,没错,该选项的设定控制着TCP的关闭形态,TCP默认是在close立即返回后,如果有数据残留在套接字的发送缓冲区中,系统将试着把这些数据发送给对端。 
JDK对该选项的定义为:

  1. public void setSoLinger(boolean on, int linger) throws SocketException;

两个参数将产生下列三种情形: 
1、 on 为 false,则该选项关闭,linger 的值被忽略,这就是TCP的缺省设置,close立即返回,如果可能将会传输未发送的数据给对端;

2、 设置on为true,linger大于0(我在很多文章中看到这里写的是非0,但Java中,给该选项设置小于0会抛出” invalid value for SO_LINGER”异常)那么当close某个连接时,内核将拖延一段时间。即linger的时间(linger的单位为秒,最大值为65535)。这里的拖延(close 阻塞)是相对于BIO来讲,如果套接字先前被设置为非阻塞(NIO),那么将不等待close完成,即使linger > 0也是如此。如果套接字发送缓冲区中仍然残留数据,那么close线程将被投入睡眠,直到所有数据都已发送完,并且均被对端确认或者拖延时间到,close才会被唤醒。 
这里有一个原则:设置SO_LINGER套接字选项后,close的成功返回只是告诉我们早先发送的数据(包括FIN)已由对端TCP确认,而不能告诉我们对端的应用进程是否已读取到数据,但如果不设置该套接字选项,那我们连对端TCP是否确认了数据都不知道;

3、 设置on为true,linger 为0,那么当close某个连接时,TCP夭折该连接。也就是说TCP将丢弃保留在发送缓冲区中的任何数据,仅仅给对端发送一个RST分节,而没有通常所说的四分组终止序列,这样一来避免了TIME_WAIT状态。 
然而在2MSL秒内创建该连接的另一个化身,会导致老的重复分节被不正确地递送到新的化身上。这样的情况,有一个替代,就是TCP的 SO_REUSEADDR选项。 

 
 
【3】.如何控制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. js自动完成
  2. Spring retry基本使用
  3. CSS3实现边框锯齿效果
  4. 百度Ueditor
  5. IBATIS动态SQL(转)
  6. hdu Train Problem I
  7. 各种语言HMAC SHA256实现
  8. 自定义Dialog,实现由下而上的弹出效果(模仿QQ退出等)
  9. C++左值
  10. gridview中后台获取某列的值
  11. SPOJ - VISIBLEBOX [multiset的使用]
  12. tpcc-mysql的使用
  13. 排序算法总结及Java实现
  14. 阿里云Centos7 apache配置
  15. spring boot - 整合jpa多对对关系保存和查询示例
  16. 基于kNN的手写字体识别——《机器学习实战》笔记
  17. Keepalived详解(五):Keepalived集群中MASTER和BACKUP角色选举策略【转】
  18. Nginx或Apache通过反向代理配置wss服务
  19. 牛客练习赛22 简单瞎搞题(bitset优化dp)
  20. Unity3D笔记 愤怒的小鸟<五> 小鸟动画+Unity3D如何设置断点调式

热门文章

  1. mysql锁机制之乐观锁(二)
  2. html-4, form 表单 输入、传文件、单选、多选、下拉菜单、文本描述、重置、submit、按钮限制输入
  3. Codeforces Round #403 (Div. 2, based on Technocup 2017 Finals) D. Innokenty and a Football League
  4. 【AngularJS】通过jsonp与webmethod交互,实现ajax验证
  5. LeetCode 7. Reverse Integer 一个整数倒叙输出
  6. JQuery归纳总结(增加中...)
  7. 使用selenium前学习HTML(3)— 属性
  8. The type javax.http.HttpServletRequest cannot be resolved.It is indirectly 解决办法
  9. 获取 config文件的节点值
  10. 构造函数挨个过 —— String()