TCP关闭链接之TIME_WAIT

TIME_WAIT发生的场景

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

经过层层剖析后,我们发现罪魁祸首就是TIME_WAIT。为什么呢?我们这个应用服务需要通过发起TCP连接 对外提供服务。每个连接会占用一个本地端口,当在高并发的情况下,TIME_WAIT状态的连接过多,多到 把本机可用的端口耗尽,应用服务对外表现的症状,就是不能正常工作了。当过了一段时间之后,处于 TIME_WAIT的连接被系统回收并关闭后,释放出本地端口可供使用,应用服务对外表现为,可以正常工 作。这样周而复始,便会出现了一会儿不可以,过一两分钟又可以正常工作的现象。

那么为什么会产生这么多的TIME_WAIT连接呢? 这要从TCP的四次挥手说起。我在文稿中放了这样一张图。

image-20201225121738725

TCP连接终止时,主机1先发送FIN报文,主机2进入CLOSE_WAIT状态,并发送一个ACK应答,同时,主机2 通过read调用获得EOF,并将此结果通知应用程序进行主动关闭操作,发送FIN报文。主机1在接收到FIN报 文后发送ACK应答,此时主机1进入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 

过了这个时间之后,主机1就进入CLOSED状态。为什么是这个时间呢?

只有发起连接终止的一方会进入TIME_WAIT状态。

TIME_WAIT的作用

你可能会问,为什么不直接进入CLOSED状态,而要停留在TIME_WAIT这个状态?

首先,这样做是为了确保最后的ACK能让被动关闭方接收,从而帮助其正常关闭。

TCP在设计的时候,做了充分的容错性设计,比如,TCP假设报文会出错,需要重传。在这里,如果图中主 机1的ACK报文没有传输成功,那么主机2就会重新发送FIN报文。

如果主机1没有维护TIME_WAIT状态,而直接进入CLOSED状态,它就失去了当前状态的上下文,只能回复 一个RST操作,从而导致被动关闭方出现错误。

现在主机1知道自己处于TIME_WAIT的状态,就可以在接收到FIN报文之后,重新发出一个ACK报文,使得 主机2可以进入正常的CLOSED状态。

第二个理由和连接“化身”和报文迷走有关系,为了让旧连接的重复分节在网络中自然消失

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

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

image-20201225122415353

所以,TCP就设计出了这么一个机制,经过2MSL这个时间,足以让两个方向上的分组都被丢弃,使得原来 连接的分组在网络中都自然消失,再出现的分组一定都是新化身所产生的。

TIME_WAIT的危害

第一是内存资源占用,这个目前看来不是太严重,基本可以忽略。

第二是对端口资源的占用,一个TCP连接至少消耗一个本地端口。要知道,端口资源也是有限的,一般可以开启的端口为32768〜61000 ,也可以通过net.ipv4.ip_local_port_range指定,如果TIME_WAIT状 态过多,会导致无法创建新连接。

如何优化TIME_WAIT?

net.ipv4.tcp_max_tw_buckets

一个暴力的方法是通过sysctl命令,将系统值调小。这个值默认为18000,当系统中处于TIME_WAIT的连接 一旦超过这个值时,系统就会将所有的TIME_WAIT连接状态重置,并且只打印出警告信息。这个方法过于 暴力,而且治标不治本,带来的问题远比解决的问题多,不推荐使用。

调低TCP_TIMEWAIT_LEN,重新编译系统

这个方法是一个不错的方法,缺点是需要“一点”内核方面的知识,能够重新编译内核

你可能感兴趣的:(TCP关闭链接之TIME_WAIT)