kernel: TCP: time wait bucket table overflow的问题剖析及解决方法

随着访问量的增大,系统默认的承受能力达到上限,这个时候就会报一些异常。比如/var/log/messages中常见的“kernel: TCP: time wait bucket table overflow”这个信息,会发现每隔5s就会报出几行。此时查看连接状态如下:

[root@ZhOu ~]# netstat -an | awk '{print $6}' | sort | uniq -c | sort -rn
  16539 TIME_WAIT
   3159 ESTABLISHED
     23 LISTEN
     20 CONNECTED
      5 STREAM
      4 
      1 I-Node
      1 Foreign
      1 established)
      1 and
      1 9793
      1 938869322
      1 8751
      1 7183
      1 7182
      1 7168
      1 10007

可以看到TIME_WAIT的量还是很高的。下面先看一下TCP连接的过程

kernel: TCP: time wait bucket table overflow的问题剖析及解决方法_第1张图片

通过此图先说明几个概念:

TIME_WAIT的产生条件:主动关闭方在发送四次挥手的最后一个ACK会变为TIME_WAIT状态,保留次状态的时间为两个MSL(linux里一个MSL为30s,是不可配置的)

TIME_WAIT两个MSL的作用:可靠安全的关闭TCP连接。比如网络拥塞,主动方最后一个ACK被动方没收到,这时被动方会对FIN开启TCP重传,发送多个FIN包,在这时尚未关闭的TIME_WAIT就会把这些尾巴问题处理掉,不至于对新连接及其它服务产生影响。

TIME_WAIT占用的资源:少量内存(查资料大概4K)和一个fd。

TIME_WAIT关闭的危害:1、 网络情况不好时,如果主动方无TIME_WAIT等待,关闭前个连接后,主动方与被动方又建立起新的TCP连接,这时被动方重传或延时过来的FIN包过来后会直接影响新的TCP连接;

2、 同样网络情况不好并且无TIME_WAIT等待,关闭连接后无新连接,当接收到被动方重传或延迟的FIN包后,会给被动方回一个RST包,可能会影响被动方其它的服务连接。

打印“time wait bucket table overflow”信息的代码:

    void tcp_time_wait(struct sock *sk, int state, int timeo)
    {
        struct inet_timewait_sock *tw = NULL;
        const struct inet_connection_sock *icsk = inet_csk(sk);
        const struct tcp_sock *tp = tcp_sk(sk);
        int recycle_ok = 0;

        if (tcp_death_row.sysctl_tw_recycle && tp->rx_opt.ts_recent_stamp)
            recycle_ok = icsk->icsk_af_ops->remember_stamp(sk);

        if (tcp_death_row.tw_count < tcp_death_row.sysctl_max_tw_buckets)
            tw = inet_twsk_alloc(sk, state);

        if (tw != NULL) {
            struct tcp_timewait_sock *tcptw = tcp_twsk((struct sock *)tw);
            const int rto = (icsk->icsk_rto << 2) - (icsk->icsk_rto >> 1);
        ....
        } else {
                /* Sorry, if we're out of memory, just CLOSE this
                 * socket up. We've got bigger problems than
                 * non-graceful socket closings.
                 */
                LIMIT_NETDEBUG(KERN_INFO "TCP: time wait bucket table overflow\n");
        } 

此段代码处于tcp套接字关闭流程,此时本机主动关闭tcp套接字,套接字状态=变为time wait,等待对端进行关闭套接字。
从代码可以看出,有两种情况可能导致这样的打印:
1、当tcp_death_row.tw_count < tcp_death_row.sysctl_max_tw_buckets时,即当当前处于time wait状态的socket数量超过sysctl_max_tw_buckets(即net.ipv4.tcp_max_tw_buckets)时
2、当inet_twsk_alloc(sk, state)返回为NULL时,出现这样的打印,再看看inet_twsk_alloc(sk, state)的代码:


    struct inet_timewait_sock *inet_twsk_alloc(const struct sock *sk, const int state)
    {
        struct inet_timewait_sock *tw =
            kmem_cache_alloc(sk->sk_prot_creator->twsk_prot->twsk_slab,
                     GFP_ATOMIC);
    ...
    return tw;
    }

由此可以看出,当kmem_cache_alloc(从slab中分配内存)失败时,会返回NULL,最终导致这样的打印,这种情况可能的原因为内存不足。
综上,打印出现有两种可能原因:
1) 处于time wait状态的tcp套接字,超过net.ipv4.tcp_max_tw_buckets限制
2) 申请内存失败,可能原因为内存不足

该打印不影响系统的正常功能,当出现这种情况时,出现异常的套接字打印完成后会立即释放,不再等对端确认关闭。但有隐患,如果表示处于time wait的socket太多,则可能导致无法创建新的连接。这种可能是业务代码在socket关闭的处理上有些问题,可能存在大量半关闭的socket。另外,也可能表示内存不足,需要关注。

解决方法

既然知道了TIME_WAIT的用意了,尽量按照TCP的协议规定来调整,对于tw的reuse、recycle其实是违反TCP协议规定的,服务器资源允许、负载不大的条件下,尽量不要打开,当出现TCP: time wait bucket table overflow,需要调整/etc/sysctl.conf中下面参数:

net.ipv4.tcp_max_tw_buckets = 50000 
调大timewait 的数量

net.ipv4.tcp_fin_timeout = 10  
如果套接字由本端要求关闭,这个参数决定了它保持在FIN-WAIT-2 状态的时间。对端可以出错并永远不关闭连接,甚至意外当机。缺省值是60 秒。2.2 内核的通常值是180 秒,3你可以按这个设置,但要记住的是,即使你的机器是一个轻载的WEB 服务器,也有因为大量的死套接字而内存溢出的风险,FIN- WAIT-2 的危险性比FIN-WAIT-1 要小,因为它最多只能吃掉1.5K 内存,但是它们的生存期长些。

net.ipv4.tcp_tw_recycle= 1   
启用timewait 快速回收

net.ipv4.tcp_tw_reuse = 1    
开启重用,允许将TIME-WAIT sockets 重新用于新的TCP 连接

net.ipv4.tcp_keepalive_time = 15  
当keepalive 启用的时候,TCP 发送keepalive 消息的频度,缺省是2 小时  

设置完毕,查看messages中的信息和连接状态,一切正常了。




参考文章

http://benpaozhe.blog.51cto.com/10239098/1767612

你可能感兴趣的:(Linux)