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連接的過程

這裏寫圖片描述

通過此圖先說明幾個概念:

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章