tcp短链接TIME_WAIT问题解决方法大全

tcp链接是网络编程中最基础的概念,基于不一样的使用场景,咱们通常区分为“长链接”和“短链接”,
长短链接的优势和缺点这里就不详细展开了,有心的同窗直接去google查询,本文主要关注如何解决tcp短链接的TIME_WAIT问题。


短链接最大的优势是方便,特别是脚本语言,因为执行完毕后脚本语言的进程就结束了,基本上都是用短链接。
但短链接最大的缺点是将占用大量的系统资源,例如:本地端口、socket句柄。
致使这个问题的缘由其实很简单:tcp协议层并无长短链接的概念,所以无论长链接仍是短链接,链接创建->数据传输->链接关闭的流程和处理都是同样的。


正常的TCP客户端链接在关闭后,会进入一个TIME_WAIT的状态,持续的时间通常在1~4分钟,对于链接数不高的场景,1~4分钟其实并不长,对系统也不会有什么影响,
但若是短期内(例如1s内)进行大量的短链接,则可能出现这样一种状况:客户端所在的操做系统的socket端口和句柄被用尽,系统没法再发起新的链接!


举例来讲:假设每秒创建了1000个短链接(Web场景下是很常见的,例如每一个请求都去访问memcached),假设TIME_WAIT的时间是1分钟,则1分钟内须要创建6W个短链接,
因为TIME_WAIT时间是1分钟,这些短链接1分钟内都处于TIME_WAIT状态,都不会释放,而Linux默认的本地端口范围配置是:net.ipv4.ip_local_port_range = 32768    61000
不到3W,所以这种状况下新的请求因为没有本地端口就不能创建了。


能够经过以下方式来解决这个问题:
1)能够改成长链接,但代价较大,长链接太多会致使服务器性能问题,并且PHP等脚本语言,须要经过proxy之类的软件才能实现长链接;
2)修改ipv4.ip_local_port_range,增大可用端口范围,但只能缓解问题,不能根本解决问题;
3)客户端程序中设置socket的SO_LINGER选项;
4)客户端机器打开tcp_tw_recycle和tcp_timestamps选项;
5)客户端机器打开tcp_tw_reuse和tcp_timestamps选项;
6)客户端机器设置tcp_max_tw_buckets为一个很小的值;


在解决php链接Memcached的短链接问题过程当中,咱们主要验证了3)4)5)6)几种方法,采起的是基本功能验证和代码验证,并无进行性能压力测试验证,
所以实际应用的时候须要注意观察业务运行状况,发现丢包、断连、没法链接等现象时,须要关注是不是由于这些选项致使的


虽然这几种方法均可以经过google查询到相关信息,但这些信息大部分都是泛泛而谈,并且绝大部分都是人云亦云,没有很大参考价值。
咱们在定位和处理这些问题过程当中,遇到一些疑惑和困难,也花费了一些时间去定位和解决,如下就是相关的经验总结。php

 

 

SO_LINGER是一个socket选项,经过setsockopt API进行设置,使用起来比较简单,但其实现机制比较复杂,且字面意思上比较难理解。
解释最清楚的当属《Unix网络编程卷1》中的说明(7.5章节),这里简单摘录:
SO_LINGER的值用以下数据结构表示:
struct linger {
     int l_onoff; /* 0 = off, nozero = on */
     int l_linger; /* linger time */linux

};编程

 

其取值和处理以下:
一、设置 l_onoff为0,则该选项关闭,l_linger的值被忽略,等于内核缺省状况,close调用会当即返回给调用者,若是可能将会传输任何未发送的数据;
二、设置 l_onoff为非0,l_linger为0,则套接口关闭时TCP夭折链接,TCP将丢弃保留在套接口发送缓冲区中的任何数据并发送一个RST给对方,
   而不是一般的四分组终止序列,这避免了TIME_WAIT状态;
三、设置 l_onoff 为非0,l_linger为非0,当套接口关闭时内核将拖延一段时间(由l_linger决定)。
   若是套接口缓冲区中仍残留数据,进程将处于睡眠状态,直 到(a)全部数据发送完且被对方确认,以后进行正常的终止序列(描述字访问计数为0)
   或(b)延迟时间到。此种状况下,应用程序检查close的返回值是很是重要的,若是在数据发送完并被确认前时间到,close将返回EWOULDBLOCK错误且套接口发送缓冲区中的任何数据都丢失。
   close的成功返回仅告诉咱们发送的数据(和FIN)已由对方TCP确认,它并不能告诉咱们对方应用进程是否已读了数据。若是套接口设为非阻塞的,它将不等待close完成。
   
第一种状况其实和不设置没有区别,第二种状况能够用于避免TIME_WAIT状态,但在Linux上测试的时候,并未发现发送了RST选项,而是正常进行了四步关闭流程,
初步推断是“只有在丢弃数据的时候才发送RST”,若是没有丢弃数据,则走正常的关闭流程。
查看Linux源码,确实有这么一段注释和源码:
=====linux-2.6.37 net/ipv4/tcp.c 1915=====
/* As outlined in RFC 2525, section 2.17, we send a RST here because
* data was lost. To witness the awful effects of the old behavior of
* always doing a FIN, run an older 2.1.x kernel or 2.0.x, start a bulk
* GET in an FTP client, suspend the process, wait for the client to
* advertise a zero window, then kill -9 the FTP client, wheee...
* Note: timeout is always zero in such a case.
*/
if (data_was_unread) {
/* Unread data was tossed, zap the connection. */
NET_INC_STATS_USER(sock_net(sk), LINUX_MIB_TCPABORTONCLOSE);
tcp_set_state(sk, TCP_CLOSE);
tcp_send_active_reset(sk, sk->sk_allocation);

另外,从原理上来讲,这个选项有必定的危险性,可能致使丢数据,使用的时候要当心一些,但咱们在实测libmemcached的过程当中,没有发现此类现象,
应该是和libmemcached的通信协议设置有关,也多是咱们的压力不够大,不会出现这种状况。


第三种状况其实就是第一种和第二种的折中处理,且当socket为非阻塞的场景下是没有做用的。
对于应对短链接致使的大量TIME_WAIT链接问题,我的认为第二种处理是最优的选择,libmemcached就是采用这种方式,
从实测状况来看,打开这个选项后,TIME_WAIT链接数为0,且不受网络组网(例如是否虚拟机等)的影响。服务器

相关文章
相关标签/搜索