UNP-TIME_WAIT那点事

TIME_WAIT

TCP 四次挥手过程中,发起连接断开的一方会有一段时间处于 TIME_WAIT 的状态,TIME_WAIT 是用来做什么的么?

现实的故障

在一次升级线上应用服务之后,我们发现该服务的可用性变得时好时坏,一段时间可以对外提供服务,一段时间突然又不可以,大家都百思不得其解。登录到服务所在的主机上,使用 netstat 命令查看后才发现,主机上有成千上万处于 TIME_WAIT 状态的连接。

原因:应用服务需要通过发起 TCP 连接对外提供服务。每个连接会占用一个本地端口,当在高并发的情况下,TIME_WAIT 状态的连接过多,多到把本机可用的端口耗尽,应用服务对外表现的症状,就是不能正常工作了。

表现:当过了一段时间之后,处于 TIME_WAIT 的连接被系统回收并关闭后,释放出本地端口可供使用,应用服务对外表现为,可以正常工作。这样周而复始,便会出现了一会儿不可以,过一两分钟又可以正常工作的现象。

为啥会有这么多TIME_WAIT?


看到了,原因就是由于TCP四次挥手,另外需要注意,只有主动终止连接的一方会处于TIME_WAIT状态。

主机 1 在 TIME_WAIT 停留持续时间是固定的,是最长分节生命期 MSL(maximum segment lifetime)的两倍,一般称之为 2MSL。和大多数 BSD 派生的系统一样,Linux 系统里有一个硬编码的字段,名称为TCP_TIMEWAIT_LEN,其值为 60 秒。也就是说,Linux 系统停留在 TIME_WAIT 的时间为固定的 60 秒。

#define TCP_TIMEWAIT_LEN (60*HZ) /* how long to wait to destroy TIME-        WAIT state, about 60 seconds  */

TIME_WAIT有什么用?

一,确保ack被对方收到,帮助对端正常关闭

TCP充分考虑容错性,如果直接进入CLOSED状态,那么对端如果没有收到ACK,会继续发送FIN报文,然后会回送RST报文导致对端异常关闭。

二,与连接化身和迷走报文有关

我们知道,在网络中,经常会发生报文经过一段时间才能到达目的地的情况,产生的原因是多种多样的,如路由器重启,链路突然出现故障等。如果迷走报文到达时,发现 TCP 连接四元组(源 IP,源端口,目的 IP,目的端口)所代表的连接不复存在,那么很简单,这个报文自然丢弃。

我们考虑这样一个场景,在原连接中断后,又重新创建了一个原连接的“化身”,说是化身其实是因为这个连接和原先的连接四元组完全相同,如果迷失报文经过一段时间也到达,那么这个报文会被误认为是连接“化身”的一个 TCP 分节,这样就会对 TCP 通信产生影响。

你可能会觉得这个概率很小,但正如死锁概率发生的很小,我们还是要规避。

TIME_WAIT的危害

一,内存资源占用(可忽略)
二,端口资源占用(主要危险)
端口资源也是有限的,一般可以开启的端口为 32768~61000 ,也可以通过net.ipv4.ip_local_port_range指定,如果 TIME_WAIT 状态过多,会导致无法创建新连接。这个也是我们在一开始讲到的那个例子。

如何优化TIME_WAIT?

net.ipv4.tcp_max_tw_buckets

暴力调小系统值。当系统中TIME_WAIT的数量超过这个值,就重置所有的TIME_WAIT连接。
注意此方法治标不治本,太过暴力,而且会带来很多附加问题。

调低 TCP_TIMEWAIT_LEN,重新编译系统

这个很牛逼,需要以为了解内核的专家,重新编译内核。

SO_LINGER 的设置

linger英文原意为“滞留”。设置关闭连接的行为;

struct linger {
 int  l_onoff;    /* 0=off, nonzero=on */
 int  l_linger;    /* linger time, POSIX specifies units as seconds */
}

l_noff 为0,关闭linger选项,这是关闭套接字的默认行为。当其为非0:

  • l_linger为0,那么调用close,会发送RST给对端,该TCP跳过四次挥手,也就跳过了TIME_WAIT,称为强行关闭。对端会收到RST会立刻得到connect reset by peer

  • l_linger为非0,那么调用close后,调用close的线程阻塞,直到数据发送出去,或者设置的l_linger计时时间到。

int setsockopt(int sockfd, int level, int optname, const void *optval,
        socklen_t optlen);

//用法

struct linger so_linger;
so_linger.l_onoff = 1;
so_linger.l_linger = 0;
setsockopt(s,SOL_SOCKET,SO_LINGER, &so_linger,sizeof(so_linger));

net.ipv4.tcp_tw_reuse:更安全的设置

Allow to reuse TIME-WAIT sockets for new connections when it is safe from protocol viewpoint. Default value is 0.It should not be changed without advice/request of technical experts.

如果从协议角度理解安全可控,那么可以复用TIME_WAIT的套接字为新的连接。
什么是协议角度理解的安全可控?

  • 只适用客户端(即连接发起方)
  • 对应的TIME_WAIT状态的连接创建时间超过1秒才可以复用

使用这个选项,还有一个前提,需要打开对 TCP 时间戳的支持,即net.ipv4.tcp_timestamps=1(默认即为 1)。

要知道,TCP 协议也在与时俱进,RFC 1323 中实现了 TCP 拓展规范,以便保证 TCP 的高可用,并引入了新的 TCP 选项,两个 4 字节的时间戳字段,用于记录 TCP 发送方的当前时间戳和从对端接收到的最新时间戳。由于引入了时间戳,我们在前面提到的 2MSL 问题就不复存在了,因为重复的数据包会因为时间戳过期被自然丢弃。

posted @ 2020-04-25 11:10  傻蜗牛  阅读(160)  评论(0)    收藏  举报