TCP TIME_WAIT状态的由来与应对

Posted dog250

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了TCP TIME_WAIT状态的由来与应对相关的知识,希望对你有一定的参考价值。

看到大量time wait状态的TCP连接是一件很讨厌的事情,对于Linux而言,解决这个问题的方法包括不限于:

  • 将time wait时间导出为sysctl参数或者socket option,并配置一个足够小的值。
  • 配置net.ipv4.tcp_max_tw_buckets为一个足够小的值。
  • 开启net.ipv4.tcp_tw_reuse和net.ipv4.tcp_tw_recycle(已淘汰)。
  • 尽可能重用TCP连接,不要轻易主动关闭。

总之,很繁琐。

我们从本质上看一下TCP的time wait问题。

首先,为什么要有time wait状态。答案很标准:

  • 原因1:为了可以正确处理对端可能重传的Fin报文。
  • 原因2:为了避免当前连接的重传报文污染重用五元组的新连接。

原因1中,为什么对端可能会重传Fin?因为发送给对端针对其Fin报文的ACK可能丢失。但很明确的是,既然已经进入了time wait,说明本端肯定已经收到了对端的Fin报文。原因1来自两端的不知情:

  • 进入time wait的本端不知道对Fin报文的ACK是否到达了对端。
  • 对端收不到Last ACK时,不知道是Fin丢失了还是对Fin的ACK丢失了。

这里埋下一个伏笔:

  • 伏笔1:如何将“不知情”这件事收敛到单独一端。

原因1说明time wait状态是必须的,接着看原因2,原因2解释了为什么time wait状态需要维持“这么久(too long to bear!)”。

在正常情况下,TCP报文在网络上停留的时间在RTT级别,按照标准的SPF路由算法,这个值的最大值在60%光速绕地球半圈的时间附近,大概400ms以内,不到一秒。但time wait却要维持分钟级别,Why?

这里需要考虑路由抖动重收敛时的异常情况,路由重收敛时间大概在分钟级别,在路由重收敛期间,路由环路是可能的,对于TCP协议而言,超过与RTT同级的RTO时间未收到ACK,会将报文判定为丢失进而重传,重传报文通过其它路由到达对端推动窗口滑动,然而原始报文并未丢失,只是进入了暂时的环路,待路由完成收敛,它依然可能会被路由到对端,这段时间可达分钟级别。

time wait维持分钟级别的原因,就是为了避免进入分钟级别环路的Fin报文抵达新建连接从而造成误杀。time wait需要保证当重用五元组的新连接建立时,旧连接的所有报文均已从网络消失,包括进入环路的报文。

再埋一个伏笔:

  • 伏笔2:有没有办法让TCP连接知道肯定没有遗留在网络上的报文呢?这种情况下,time wait是否还有必要?

综上得知,TCP的time wait状态是必要的,且需要维持“那么久”的时间。这就是关于TCP time wait状态的本质。

现在基于上文的两处伏笔,从协议层面来看,如何优化这个time wait状态,换句话说,如果重新设计一个传输协议,如何处理time wait状态可使新协议看起来更优,在协议设计的层面上有效降低time wait状态连接的数量。目标看起来没什么花样:

  • 将肯定不需要进入time wait状态的连接识别出来,直接关闭。

达到目标的手段就是重新设计挥手过程,和TCP不同的地方就两点:

  • 由被动关闭端处理time wait状态。
  • 主动关闭端的Last ACK携带是否重传过Fin报文的标识。

改进版TCP协议的挥手状态机如下:

时序图如下:

通过以上修改,time wait状态的连接将大大减少。只要在挥手阶段Fin报文没有被重传,均不用再经过time wait状态,而Fin报文的重传是低概率事件。

只解释几个细节:

  • 主动关闭端在FIN-WAIT1,FIN-WAIT2,CLOSING状态只要发完Last ACK即可立即释放连接。因为主动端已经明确自己的挥手任务完成了,且time wait状态由被动端维护。“不知情”收敛到一端。
  • 如果Last ACK丢失,被动端重传Fin报文,由于主动端连接已经释放,重传的Fin报文被Reset,被动端如果收到Reset,即得知主动端完成挥手,但由于重传过Fin报文,则不得不维持time wait状态。
  • 如果Last ACK丢失,被动端重传Fin报文,由于主动端连接已经释放,重传的Fin报文被Reset,Reset如果丢失,被动端会继续重传或直到Last ACK超时,被动端不得不维持time wait状态。
  • 主动端如果从未重传过Fin,则会在Last ACK中置位一标识,被动端收到后结合自己是否重传过Fin报文的情况,决定是否可立即释放连接。
  • 只需通过判断是否重传过Fin报文来决定是否立即释放连接。背后的依据是,如果数据报文污染了连接,识别并丢弃旧连接的数据是应用层的职责。

悟以往之不谏,知来者亦不可追。现实中,并无可能去修改双边,协议便无法拓展,但业务既不想看到大量的time wait,又不想看到dmesg里的报错(tcp_max_tw_buckets超了会报错)。

我依然坚持建议业务用Reset来关闭TCP连接(通过TCP_LINGER2这个TCP socket option可支持),简单清净。否则就是没完没了的FIN-WAIT1/2,Last ACK,time wait等一大堆给业务解释1000遍也还会再问第1001遍的复杂,无聊,一点都不高端且毫无可用性又容易引起故障的挥手状态。
污染下一个重用五元组的连接并不可怕,重连就是了。

浙江温州皮鞋湿,下雨进水不会胖。

以上是关于TCP TIME_WAIT状态的由来与应对的主要内容,如果未能解决你的问题,请参考以下文章

TCP中的TIME_WAIT状态

【TCP】 tcp四次挥手状态 TIME_WAIT

聊一聊TCP协议的TIME_WAIT与性能优化

内核 TCP 参数调优

关于tcp中time_wait状态的4个问题

TCP TIME_WAIT 详解