1. 程式人生 > >kernel: TCP: time wait bucket table overflow的問題剖析及解決方法

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中的資訊和連線狀態,一切正常了。




參考文章