zzouqb
作者zzouqb2020-07-08 18:50
系统运维工程师, 光大银行信息科技部

由优化FIN_WAIT_2状态超时引入的关于tcp_fin_timeout参数研究

字数 22309阅读 1850评论 0赞 3

背景

在很多实际应用环境中,我们经常会遇到系统中存在大量的 FIN_WAIT_2 状态的连接,由于不能及时释放, 造成本端不能提供有效端口资源,影响对端建立新连接的情况。

对于上述情况,引起的可能原因有:

1、 比如 服务端主动关闭连接,但客户端没有关闭连接

2、 比如 有一些客户端在处理持久连接 (aka keepalives) 时存在问题,当连接空闲下来服务器关闭连接时 ( 基于 KeepAlive Timeout 指令 ) , 客户端的程序没有主动发送 FIN 和 ACK 到服务器,这样就意味着这个连接将停留在 FIN_WAIT_2 状态

诸如此类的原因,经常会导致系统中 FIN_WAIT_2 连接过多。

有一天,小明问我,他的系统中 FIN_WAIT_2 状态的连接很多,把 tcp_fin_timeout 调整成小点的值,也没立竿见影。

我: 把 man 文档找出来发给小明,说上面说的很清楚,就是用来控制 FIN_WAIT_2 超时的,怎么可能没效果呢,不会是设置错了吧?

小明:。。。

我:测试看看。

测试开始之前,先看看 TCP 的状态机。

FIN_WAIT_2 如何产生

根据 TCP 四次握手协议,正常情况下在主动关闭端发送了 FIN 之后,进入 FIN_WAIT_1 状态,在此状态下收到对端的 ACK ,则进入 FIN_WAIT_2 状态,而 FIN_WAIT_2 后续要做的工作是等待接收对端发过来的 FIN 包,并且发送 ACK ,进而进入到 TIME_WAIT 状态,并最终进入释放连接处理流程,如下为客户端主动发起关闭连接的状态变化图:

异常情况下,如果被动关闭端处于 CLOSE-WAIT 状态后,没有及时返回 FIN ACK 至主动关闭端,导致主动关闭端一直处于 FIN_WAIT2 状态:

这是比较典型的导致主动关闭端处于 FIN_WAIT2 状态的场景,在系统里,一旦此状态的连接不断增多,就会导致出现文章开头提到的连接建连异常。

本文主要关注 FIN_WAIT_2 状态连接的处理,并结合 Linux 系统中 tcp_fin_timeout 参数,探索相应的解决方案。

场景再现

测试场景:

在测试环境中,我们在客户端和服务端建立连接后,以 Server 端主动断开连接, Client 端保持一定时间后再释放连接的方式来模拟 FIN_WAIT_2 状态的产生。

同时,我们也通过对 Linux 上 TCP 内核参数的了解,有一个参数可以控制系统上 FIN_WAIT_2 状态连接的超时时间,以使 FIN_WAIT_2 状态连接按要求超时,并进入释放流程。

实际测试:

由于在实际应用环境中,我们经常会遇到系统中存在大量的 FIN_WAIT_2 状态连接的情况,为快速释放该状态的连接,我们手工把 tcp_fin_timeout 参数调整为 5 ( tcp_fin_timeout=5 ),期待结果应该是处于 FIN_WAIT2 状态的连接,最多保持 5 秒就应该转入 TIME_WAIT 状态 , 测试过程及输出如下:

测试环境 :

Server和Client使用的操作系统版本为SUSE Linux 11 SP4
系统内核参数( net.ipv4.tcp_fin_timeout )设置如下:

 root@linux:/root# sysctl-a | grep fin_time 
 net.ipv4.tcp_fin_timeout = 5

11.4 测试代码如下 (12.4 测试代码把 myHost 设置成 12.4 所在的服务器 ip: 192.168.1.4 ) :

Server 端测试代码,见《 Server.py 》:

from socket import *
import time
myHost=' 192.168.2.252 '
myPort=6667
sockobj=socket(AF_INET,SOCK_STREAM)
sockobj.bind((myHost,myPort))
sockobj.listen(5)
connection,address=sockobj.accept()
time.sleep(1)
connection.close()

Client 端测试代码 ( 不调用 close ,也不退出 ) ,见《 Client.py 》:

from socket import *
import time
myHost= '192.168.2.252 '
myPort=6667
sockobj=socket(AF_INET,SOCK_STREAM)
sockobj.connect((myHost,myPort))
time.sleep(10)
sockobj.close()

执行代码

Server 端执行命令如下:

root@linux:/root#python server.py

Cient 端执行命令如下:

b7600043:/root#python client.py

测试结果

Server 端的输出结果如下:

root@linux:/root#while true;do netstat -ano|grep "10.200.68";sleep 1;done
tcp        0      0 192.168.2.252:6667     192.168.1.4:60326        ESTABLISHED off (0.00/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60326        FIN_WAIT2   keepalive (4.16/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60326        FIN_WAIT2   keepalive (3.15/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60326        FIN_WAIT2   keepalive (2.14/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60326        FIN_WAIT2   keepalive (1.13/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60326        FIN_WAIT2   keepalive (0.12/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60326        FIN_WAIT2   timewait (4.10/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60326        FIN_WAIT2   timewait (3.09/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60326        FIN_WAIT2   timewait (2.08/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60326        FIN_WAIT2   timewait (1.07/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60326        FIN_WAIT2   timewait (0.06/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60326        FIN_WAIT2   timewait (0.00/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60326        FIN_WAIT2   timewait (0.00/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60326        FIN_WAIT2   timewait (0.00/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60326        FIN_WAIT2   timewait (0.00/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60326        FIN_WAIT2   timewait (0.00/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60326        FIN_WAIT2   timewait (0.00/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60326        FIN_WAIT2   timewait (0.00/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60326        FIN_WAIT2   timewait (0.00/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60326        FIN_WAIT2   timewait (0.00/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60478        TIME_WAIT   timewait (59.35/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60478        TIME_WAIT   timewait (58.34/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60478        TIME_WAIT   timewait (57.33/0/0)
……

Client 端的输出结果如下:

tcp        0      0 192.168.1.4:60362        192.168.2.252:6667     ESTABLISHED off (0.00/0/0)
tcp        1      0 192.168.1.4:60362        192.168.2.252:6667     CLOSE_WAIT  off (0.00/0/0)
tcp        1      0 192.168.1.4:60362        192.168.2.252:6667     CLOSE_WAIT  off (0.00/0/0)
tcp        1      0 192.168.1.4:60362        192.168.2.252:6667     CLOSE_WAIT  off (0.00/0/0)
tcp        1      0 192.168.1.4:60362        192.168.2.252:6667     CLOSE_WAIT  off (0.00/0/0)
tcp        1      0 192.168.1.4:60362        192.168.2.252:6667     CLOSE_WAIT  off (0.00/0/0)
tcp        1      0 192.168.1.4:60362        192.168.2.252:6667     CLOSE_WAIT  off (0.00/0/0)
tcp        1      0 192.168.1.4:60362        192.168.2.252:6667     CLOSE_WAIT  off (0.00/0/0)
tcp        1      0 192.168.1.4:60362        192.168.2.252:6667     CLOSE_WAIT  off (0.00/0/0)
tcp        1      0 192.168.1.4:60362        192.168.2.252:6667     CLOSE_WAIT  off (0.00/0/0)

通过测试过程及输出结果可以看到, Server 端 FIN_WAIT2 状态的连接并没有在 5 秒 (net.ipv4.tcp_fin_timeout = 5) 后超时进入 TIME_WAIT 状态,实际上还是等到 Client 端经过 10 秒睡眠结束(代码中为模拟 Client 端未关闭的场景,进行了 time.sleep(10) 动作),调用 close()后才进入 TIME_WAIT 状态 ( 此时 server 端 tcp_fin_timeout 超时还未完成,如果超时完成了则不会再进入 TIME_WAIT 状态 ) 。

初步看该测试结果并没有达到我们的预期效果,似乎在 SUSE Linux 11 SP4 (这里我们的测试环境操作系统版为 SUSE Linux 11 SP4 )中内核参数 tcp_fin_timeout 并不生效,实际情况又是如何呢?

未达预期原因

针对上述测试,我们存在两个疑问:

1 、 net.ipv4.tcp_fin_timeout 参数为什么不生效?

2 、该参数只是在 SUSE Linux 11 SP4 系统上不生效吗?

开源的好处就是可以随时查看源代码。带着问题,对操作系统源代码进行了走读,看到了和我们最初理解不一样的实现:

根据对操作系统核心代码可以看到,有两个地方可以设置 FIN_WAIT2 计时器,一个在 close_tcp() 函数中,另一个在 tcp_rcv_state_process() 函数中,通过代码我们可以看到关于 tcp_fin_timeout 参数的具体使用:

关于两个函数代码如下:

tcp_close 函数代码

void tcp_close(struct sock *sk, long timeout)
{
...
if (sk->sk_state == TCP_FIN_WAIT2) {
     struct tcp_sock *tp = tcp_sk(sk);
     if (tp->linger2 < 0) { 
         tcp_set_state(sk, TCP_CLOSE);
         tcp_send_active_reset(sk, GFP_ATOMIC);
         NET_INC_STATS_BH(sock_net(sk),
               LINUX_MIB_TCPABORTONLINGER);
     } else {
          const int tmo = tcp_fin_time(sk);
if (tmo > TCP_TIMEWAIT_LEN) {
           inet_csk_reset_keepalive_timer(sk,
               tmo - TCP_TIMEWAIT_LEN); <== FIN_WAIT2 timer set
       } else {
           tcp_time_wait(sk, TCP_FIN_WAIT2, tmo);
……
}

tcp_rcv_state_process 函数代码

int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,struct tcphdr *th, unsigned len) 
{
...
case TCP_FIN_WAIT1:
     if (tp->snd_una == tp->write_seq) {
         tcp_set_state(sk, TCP_FIN_WAIT2);
         sk->sk_shutdown |= SEND_SHUTDOWN;
         dst_confirm(__sk_dst_get(sk));

         if (!sock_flag(sk, SOCK_DEAD))
              /* Wake up lingering close() */
              sk->sk_state_change(sk);
         else {
              int tmo;

              if (tp->linger2 < 0 || (TCP_SKB_CB(skb)->end_seq != TCP_SKB_CB(skb)->seq &&after(TCP_SKB_CB(skb)->end_seq - th->fin, tp->rcv_nxt)))
 {
                   tcp_done(sk);
                   NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPABORTONDATA);
                   return 1;
               }

             tmo = tcp_fin_time(sk);
              if (tmo > TCP_TIMEWAIT_LEN) {
                  inet_csk_reset_keepalive_timer(sk, tmo - TCP_TIMEWAIT_LEN); <== FIN_WAIT2 timer set
               } else if (th->fin || sock_owned_by_user(sk)) {
...
}

上述两个函数都会调用 inet_csk_reset_keepalive_timer() 函数设置计时器,实际的超时时间由 (tmo - TCP_TIMEWATI_LEN) 决定, tmo 通过函数 tcp_fin_time() 获取,关于 tcp_fin_time() 函数代码如下:

static inline int tcp_fin_time(const struct sock *sk)
{
     int fin_timeout = tcp_sk(sk)->linger2 ? : sysctl_tcp_fin_timeout;
     const int rto = inet_csk(sk)->icsk_rto;
     if (fin_timeout < (rto << 2) - (rto >> 1))
         fin_timeout = (rto << 2) - (rto >> 1);
     return fin_timeout;
} 

/proc/sys/net/ipv4/tcp_fin_timeout 参数设置值,即我们在最初设置的 ne.ipv4.tcp_fin_timeout, 其中默认的 linger2 的值实际上与 /proc/sys/net/ipv4/tcp_fin_timeout 是一样的。
从上面 tcp_close 函数代码 ,我们可以看到如下处理分支:

    当 tmo=sysctl_tcp_fin_timeout > TCP_TIMEWAIT_LEN // TCP_TIMEWAIT_LEN 为 60*HZ, 即 60 秒 时,进入分支:
    inet_csk_reset_keepalive_timer ,超时值为 tmo-60, 并从 FIN_WAIT2 状态进入到 TIME_WAIT 状态,对于该分支则在正常的超时后进入 TIME_WAIT 状态。

    当 tmo=sysctl_tcp_fin_timeout<=  TCP_TIMEWAIT_LEN ,即 <=60 时,进入分支:
    tcp_time_wait(sk, TCP_FIN_WAIT2, tmo) –> inet_twsk_schedule

对于该分支会进入 tcp_time_wait ()函数,我们可以看到如下代码段:

/* Get the TIME_WAIT timeout firing. */
if (timeo < rto)
   timeo = rto;

if (recycle_ok) {
       tw->tw_timeout = rto;
} else {
       tw->tw_timeout = TCP_TIMEWAIT_LEN;
       if (state == TCP_TIME_WAIT)
           timeo = TCP_TIMEWAIT_LEN;
    }

       inet_twsk_schedule(tw, &tcp_death_row, timeo,TCP_TIMEWAIT_LEN);
       inet_twsk_put(tw);
} else {

      ......
}

tcp_minisocks.c 文件中定义的全局变量 struct inet_timewait_death_row tcp_death_row 会统一接管系统中的 time_wait 套接口,它将负责这些套接口的数量限制、删除、释放等操作,上图标灰部分,即把我们当前处于 time_wait 的套接口赋予结构 inet_timewait_death_row 。

在进一步分析之前,我们先来了解下 tcp_death_row 的回收机制:

关于 inet_timewait_death_row struct 的定义如下:

struct inet_timewait_death_row tcp_death_row = {
        .sysctl_max_tw_buckets = NR_FILE * 2,
        .period         = TCP_TIMEWAIT_LEN / INET_TWDR_TWKILL_SLOTS,
        .death_lock     = __SPIN_LOCK_UNLOCKED(tcp_death_row.death_lock),
        .hashinfo       = &tcp_hashinfo,
        .tw_timer       = TIMER_INITIALIZER(inet_twdr_hangman, 0,
                                            (unsigned long)&tcp_death_row),
        .twkill_work    = __WORK_INITIALIZER(tcp_death_row.twkill_work,
                                             inet_twdr_twkill_work),
/* Short-time timewait calendar */

        .twcal_hand     = -1,
        .twcal_timer    = TIMER_INITIALIZER(inet_twdr_twcal_tick, 0,
                                            (unsigned long)&tcp_death_row),
};

在 tcp_death_row 中存在两种回收机制,一种是 timeout 较长的套接口,放入 tw_timer 定时器的队列中,一种 timeout 较短的套接口,放入 twcal_timer 定时器的队列中; tw_timer 定时器超时精度为 TCP_TIMEWAIT_LEN / INET_TWDR_TWKILL_SLOTS= 7.5 秒; 而 twcal_timer 的定时单位并不是固定的值,而是根据常量 HZ 定义的(在 SUSE11 、 12 里面 HZ=250 ), 超时精度为

1<<INET_TWDR_RECYCLE_TICK个tick

即 (1<<5)=32 个 tick, 也就是相当于约 32/250≈1/8 秒的精度。
那又是如何判断把不同 time_wait 套接口放入相应队列的呢?

suse11中 :

inet_twsk_schedule()函数关键代码进一步解读( 注意 suse12 sp3 中实现不一样,见后面解读 ):

void inet_twsk_schedule(struct inet_timewait_sock *tw,
                       struct inet_timewait_death_row *twdr,
                       const int timeo, const int timewait_len)
{
struct hlist_head *list;
    int slot;

slot = (timeo + (1 << INET_TWDR_RECYCLE_TICK) - 1) >> INET_TWDR_RECYCLE_TICK;
    该操作以1/8秒为单位向上取整
    spin_lock(&twdr->death_lock);

    /* Unlink it, if it was scheduled */
    if (inet_twsk_del_dead_node(tw))
       twdr->tw_count--;
    else
       atomic_inc(&tw->tw_refcnt);

if (slot >= INET_TWDR_RECYCLE_SLOTS) {
如果大于等于4s,进入tw_timer超时队列相应位置
       /* Schedule to slow timer */
       if (timeo >= timewait_len) {
          slot = INET_TWDR_TWKILL_SLOTS - 1;
       } else {
          slot = DIV_ROUND_UP(timeo, twdr->period); period为7s(同样是tick形式),以该分辨率为单位向上取整
          if (slot >= INET_TWDR_TWKILL_SLOTS)
             slot = INET_TWDR_TWKILL_SLOTS - 1;
          }
          tw->tw_ttd = jiffies + timeo;
          slot = (twdr->slot + slot) & (INET_TWDR_TWKILL_SLOTS - 1); 找到合适槽位,由于最多8个槽位,所以所有time_wait不会超过TCP_TIMEWAIT_LEN=60s
          list = &twdr->cells[slot];
} else {
如果小于4s,进入twcal_timer超时对列中相应位置
       tw->tw_ttd = jiffies + (slot << INET_TWDR_RECYCLE_TICK);

       if (twdr->twcal_hand < 0) {
          twdr->twcal_hand = 0;
          twdr->twcal_jiffie = jiffies;
          twdr->twcal_timer.expires = twdr->twcal_jiffie +
(slot << INET_TWDR_RECYCLE_TICK);
          add_timer(&twdr->twcal_timer);
       } else {
          if (time_after(twdr->twcal_timer.expires,
             jiffies + (slot << INET_TWDR_RECYCLE_TICK)))
               mod_timer(&twdr->twcal_timer,
                    jiffies + (slot << INET_TWDR_RECYCLE_TICK));
             slot = (twdr->twcal_hand + slot) & (INET_TWDR_RECYCLE_SLOTS - 1);
          }
          list = &twdr->twcal_row[slot];
       }

       hlist_add_head(&tw->tw_death_node, list);

       if (twdr->tw_count++ == 0)
          mod_timer(&twdr->tw_timer, jiffies + twdr->period);
       spin_unlock(&twdr->death_lock);
}

通过上面的代码可以看到如下的判断逻辑:

slot定义:slot = (timeo + (1 << INET_TWDR_RECYCLE_TICK) - 1) >> INET_TWDR_RECYCLE_TICK;
//slot以1/8秒为单位取整
……
if (slot >= INET_TWDR_RECYCLE_SLOTS) {//如果大于4s,进入tw_timer队列
……
//解读: 总共8个slot, 以大约7秒为超时精度,不同的tcp_fin_timeout进入到不同的slot中。即第一个slot的超时时间约为7s,第二个slot超时时间约为14s,依次类推,按照此超时实现原理,可能的超时误差在7 s左右,不超过14s。
}
else{//如果小于4s, 进入twcal_timer对列,超时精度为1/8s。
……
}

关于 slot 的落点示意图如下:

现在来计算不同的tcp_fin_timeout值对应的超时时间:

tcp_fin_timeout设置为3s的时候,上述代码代入如下:
slot = (3*HZ + (1 << INET_TWDR_RECYCLE_TICK) - 1) >> INET_TWDR_RECYCLE_TICK;
slot=(3*250 + (1<<5) -1) >> 5 , 按二进制表示为11000
同样当tcp_fin_timeout设置为4s的时候,代入后slot值的二进制表示为100000。

从kernel中可以看到如下定义:

#define INET_TWDR_RECYCLE_SLOTS_LOG  5
#define INET_TWDR_RECYCLE_SLOTS (1 << INET_TWDR_RECYCLE_SLOTS_LOG)

从上可以知道如下初步结果:

当tcp_fin_timeout = 3的时候,(slot = 11000) < INET_TWDR_RECYCLE_SLOTS, 从而进入twcal_timer超时队列,从前面的例子可以保持在FIN_WAIT2状态的时间基本上就是3秒钟。
当tcp_fin_timeout >= 4的时候,(slot> = 100000) >= INET_TWDR_RECYCLE_SLOTS ,从而进入tw_timer超时队列,从前面的例子看保持在FIN_WAIT2状态的时间基本上是tcp_fin_timeout 时间再加上一定的定时器精度(从上可知以7秒为单位)误差时间。
当tcp_fin_timeout > 60的情况从上面分析可知,会进入函数inet_csk_reset_keepalive_timer)并设置keepalive定时器,然后再进入tcp_time_wait()函数,其保持在FIN_WAIT2状态的时间基本上为(tcp_fin_timeout -60)时间再加上一定的定时器精度,具体如下:
当tcp_fin_timeout -60<=3时,超时时间(tcp_fin_timeout -60)
当tcp_fin_timeout -60 >=4时,时间精度以7秒为单位做为误差时间。

SUSE 12 SP3中:

inet_twsk_schedule ()函数进行了简化,直接根据 timeo 设置超时 timer , 关键代码解读:

Void __inet_twsk_schedule(struct inet_timewait_sock *tw, int timeo, bool rearm)
{   
tw->tw_kill = timeo <= 4*HZ;
    if (!rearm) {
        BUG_ON(mod_timer_pinned(&tw->tw_timer, jiffies + timeo));
        atomic_inc(&tw->tw_dr->tw_count);
    } else {
        mod_timer_pending(&tw->tw_timer, jiffies + timeo);
    }
}

结论

通过上面的测试以及对操作系统源代码的分析,我们初步可以得到如下结论:不同的 kernel 版本中, tcp_fin_timeout 的实现机制不完全一样。

一、在 SUSE11(kernel 版本 3.0.101-108.77) 中,实际的 FIN_WAIT_2 状态连接超时时间根据 net.ipv4.tcp_fin_timeout 设置的值不同而分为三种情况:

l) tcp_fin_timeout <= 3, FIN_WAIT_2 状态超时时间为 tcp_fin_timeout 值。

下图以 server 端 tcp_fin_timeout=3 为例(调整一下 client 段代码,循环执行 time.sleep(10) ,即既不 close ,也不退出程序,纯粹测 tcp_fin_timeout 的超时表现 ):

root@linux:/root#while true;do netstat -ano|grep " 192.168.2";sleep 1;done
tcp        0      0 192.168.2.252:6667     192.168.1.4:60184        ESTABLISHED off (0.00/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60184        FIN_WAIT2   timewait (2.76/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60184        FIN_WAIT2   timewait (1.76/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60184       FIN_WAIT2   timewait (0.74/0/0)
其伪代码逻辑如下:
 # define INET_TWDR_RECYCLE_TICK (8 + 2 - INET_TWDR_RECYCLE_SLOTS_LOG) // 8+2-5 == 5

slot = (timeo + (1 << INET_TWDR_RECYCLE_TICK) - 1) >> INET_TWDR_RECYCLE_TICK; // (timeo +31)/32 以上取整 [timeo/32]

连续两个 sock 进入 FIN_WAIT2 状态,并且前一个 socket 在 FIN_WAIT2 未超时的情况下,会以前面的 socket 的 timer 的超时中处理 twcal_cow 队列。

连续两个 sock 进入 FIN_WAIT2 状态,并且前一个 socket 在 FIN_WAIT2 未超时的情况下,会以前面的 socket 的 timer 的超时中处理 twcal_cow 队列。

2)  3<tcp_fin_timeout <=60, FIN_WAIT_2状态超时时间为 tcp_fin_timeout值+定时器精度(以7秒为单位)误差时间。

下图以server端 tcp_fin_timeout=7为例:

tcp        0      0 192.168.2.252:6667     192.168.1.4:60404        ESTABLISHED off (0.00/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60404        FIN_WAIT2   timewait (6.59/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60404        FIN_WAIT2   timewait (5.58/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60404        FIN_WAIT2   timewait (4.57/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60404        FIN_WAIT2   timewait (3.56/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60404        FIN_WAIT2   timewait (2.55/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60404        FIN_WAIT2   timewait (1.54/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60404        FIN_WAIT2   timewait (0.52/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60404        FIN_WAIT2   timewait (0.00/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60404        FIN_WAIT2   timewait (0.00/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60404        FIN_WAIT2   timewait (0.00/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60404        FIN_WAIT2   timewait (0.00/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60404        FIN_WAIT2   timewait (0.00/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60404        FIN_WAIT2   timewait (0.00/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60404        FIN_WAIT2   timewait (0.00/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60404        FIN_WAIT2   timewait (0.00/0/0)

其伪代码逻辑如下:

slot = (timeo + (1<<INET_TWDR_RECYCLE_TICK)-1) >>INET_TWDR_RECYCLE_TICK; //取[timeo/32]
if slot >= 32 && timeo <= 60*HZ
twdr->cells[8]有8个队列,每twdr->period周期(即7秒)取一个队列的socket进行处理

3) tcp_fin_timeout > 60, FIN_WAIT_2状态会先经历keepalive状态,持续时间为tmo=tcp_fin_timeout-60值, 再经历timewait状态,持续时间为 (tcp_fin_timeout -60)+定时器精度,这里的定时器精度根据(tcp_fin_timeout -60)的计算值,会最终落在上述两个精度范围(1/8秒为单位或7秒为单位)。

下图以 server 端 tcp_fin_timeout=65 为例:

tcp        0      0 192.168.2.252:6667     192.168.1.4:60434        ESTABLISHED off (0.00/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60434        FIN_WAIT2   keepalive (4.86/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60434        FIN_WAIT2   keepalive (3.85/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60434        FIN_WAIT2   keepalive (2.83/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60434        FIN_WAIT2   keepalive (1.82/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60434        FIN_WAIT2   keepalive (0.81/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60434        FIN_WAIT2   timewait (4.80/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60434        FIN_WAIT2   timewait (3.78/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60434        FIN_WAIT2   timewait (2.78/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60434        FIN_WAIT2   timewait (1.77/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60434        FIN_WAIT2   timewait (0.76/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60434        FIN_WAIT2   timewait (0.00/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60434        FIN_WAIT2   timewait (0.00/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60434        FIN_WAIT2   timewait (0.00/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60434        FIN_WAIT2   timewait (0.00/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60434        FIN_WAIT2   timewait (0.00/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60434        FIN_WAIT2   timewait (0.00/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60434        FIN_WAIT2   timewait (0.00/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60434        FIN_WAIT2   timewait (0.00/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60434        FIN_WAIT2   timewait (0.00/0/0)
tcp        0      0 192.168.2.252:6667     192.168.1.4:60434        FIN_WAIT2   timewait (0.00/0/0)

其伪代码逻辑如下:

set_timer(sk->sk_timer,jiffies+(timeo-60))
tcp_keeplive_timer-->tcp_time_wait(sk, TCP_FIN_WAIT2, (timeo -60))

因此 FIN_WAIT_2 状态连接超时周期也会随着 net.ipv4.tcp_fin_timeout 参数设置的不同发生变化,从而对操作系统上处于 FIN_WAIT_2 状态的连接超时产生不同的超时作用。

二、在 SUSE12 SP3(kernel 版本 4.4.156-94.64) 中,实际的 FIN_WAIT_2 状态连接超时时间根据 net.ipv4.tcp_fin_timeout 设置的值不同而分为两种情况:

1) tcp_fin_timeout <=60, FIN_WAIT_2 状态超时时间为 tcp_fin_timeout 值。下图为 server 端 tcp_fin_timeout=3 示例:

root@linux:/root#while true;do netstat -ano|grep " 192.168.1";sleep 1;done
tcp        0      0 192.168.1.4:6667         192.168.2.252:55180    ESTABLISHED off (0.00/0/0)
tcp        0      0 192.168.1.4:6667         192.168.2.252:55180    FIN_WAIT2   timewait (2.16/0/0)
tcp        0      0 192.168.1.4:6667         192.168.2.252:55180    FIN_WAIT2   timewait (1.14/0/0)
tcp        0      0 192.168.1.4:6667         192.168.2.252:55180    FIN_WAIT2   timewait (0.13/0/0)

2) tcp_fin_timeout > 60, FIN_WAIT_2 状态会先经历 keepalive 状态,持续时间为 tmo=tcp_fin_timeout-60 值 , 再经历 timewait 状态,持续时间同样为 tmo= tcp_fin_timeout-60 值。

下图为 server 端 tcp_fin_timeout=65 示例:

root@linux:/root#while true;do netstat -ano|grep " 192.168.1";sleep 1;done
tcp        0      0 192.168.1.4:6667         192.168.2.252:32939    ESTABLISHED off (0.00/0/0)
tcp        0      0 192.168.1.4:6667         192.168.2.252:32939    FIN_WAIT2   keepalive (4.77/0/0)
tcp        0      0 192.168.1.4:6667         192.168.2.252:32939    FIN_WAIT2   keepalive (3.76/0/0)
tcp        0      0 192.168.1.4:6667         192.168.2.252:32939    FIN_WAIT2   keepalive (2.74/0/0)
tcp        0      0 192.168.1.4:6667         192.168.2.252:32939    FIN_WAIT2   keepalive (1.73/0/0)
tcp        0      0 192.168.1.4:6667         192.168.2.252:32939    FIN_WAIT2   keepalive (0.71/0/0)
tcp        0      0 192.168.1.4:6667         192.168.2.252:32939    FIN_WAIT2   timewait (4.70/0/0)
tcp        0      0 192.168.1.4:6667         192.168.2.252:32939    FIN_WAIT2   timewait (3.68/0/0)
tcp        0      0 192.168.1.4:6667         192.168.2.252:32939    FIN_WAIT2   timewait (2.67/0/0)
tcp        0      0 192.168.1.4:6667         192.168.2.252:32939    FIN_WAIT2   timewait (1.65/0/0)
tcp        0      0 192.168.1.4:6667         192.168.2.252:32939    FIN_WAIT2   timewait (0.64/0/0)

如果觉得我的文章对您有用,请点赞。您的支持将鼓励我继续创作!

3

添加新评论0 条评论

Ctrl+Enter 发表
相关推广
  • 2020 容器云职业技能大赛运维岗课程系列之—容器云平台的日常故障处理
    近年来,容器技术的发展也非常迅猛,以docker和kubernetes为代表的容器技术日新月异,但我们也在容器的使用过程中碰到各种困扰和难题。本文针对Docker容器和kubernetes集群安装,部署,维护过程中,产生的问题和故障,做出有针对性的说明和解决方案,希望可以帮助到大家去快速定位和解决类似问题故障。
  • 核心数据库服务器选型优先顺序调查

    发表您的选型观点,参与即得50金币。

    作者其他文章