天天看點

為何用戶端突然出現大量TIME_WAIT堆積梳理問題排查TCP TIME_WAIT的快速回收總結

本文介紹了一個在阿裡雲環境下某用戶端ECS機器上突然發現TIME_WAIT突然增高的問題和排查過程。

問題場景:原來用戶端直接通路後端Web伺服器,TIME_WAIT數量非常少。現在引入了7層SLB來實作對後端伺服器的負載均衡。用戶端SLB通路後端伺服器,但是發現用戶端的TIME_WAIT狀态的socket很快累積到4000多個,并且客戶反映沒有修改任何核心參數。

梳理問題

收到這個資訊後,基本上可以推斷出來的資訊:

用戶端通過短連接配接連接配接SLB(伺服器),用戶端是連接配接的主動關閉方。并且并發比較大。

如果之前沒有發現TIME_WAIT堆積,而現在堆積,在通路模式不變的情況下,極有可能之前有TIME_WAIT socket的快速回收或複用。那麼基本上可以推斷下面幾個TCP核心參數設定大機率如下:

  • net.ipv4.tcp_tw_recycle = 1
  • net.ipv4.tcp_tw_reuse = 1
  • net.ipv4.tcp_timestamps = 1

客戶确認了如上資訊。

排查

在這個案例中我們目前能确認的唯一變化就是引入了SLB,那就需要看下SLB對TIME_WAIT的影響到底是什麼。對于用戶端來說,如果TCP核心參數tcp_tw_recycle和tcp_timestamps同時為1,正常情況下處于TIME_WAIT狀态的socket會被快速回收 (這個描述不嚴謹,後面可以看看源代碼),客戶現在的現象是看起來是TIME_WAIT狀态的socket沒有被快速回收。

因為tcp_tw_recycle是一個系統參數,而timestamp是TCP option裡攜帶的一個字段,我們主要需要關注下timestamp的變化。如果你足夠熟悉SLB,可能并不需要抓包就知道SLB對TCP timestamp做了什麼。但是我們這裡按照正常的排查方法:抓包,比較在引入SLB之前和之後封包有什麼差別。

在引入SLB後,用戶端通路SLB,在用戶端抓包。可以看到如下用戶端到SLB的SYN,裡面的TCP Option中包含有Timestamps。TSval是根據本端系統的CPU tick得出的值;TSecr是echo對方上次發過來的TSval值,根據對端系統的CPU tick得出,以便計算RTT,這裡因為是首個建立TCP連接配接的SYN,是以TSecr值為0。

為何用戶端突然出現大量TIME_WAIT堆積梳理問題排查TCP TIME_WAIT的快速回收總結

而在用戶端觀察SLB的回包時,可以看到TCP Option中的TCP tiemstamps已經不存在了,而用戶端在直接通路後端伺服器時TCP tiemstamps是一直存在。通過對比,發現這就是引入SLB後帶來的變化:SLB抹去了TCP Option中的timestamps字段。

為何用戶端突然出現大量TIME_WAIT堆積梳理問題排查TCP TIME_WAIT的快速回收總結

排查到這裡,我們差不多把這個變化的因素抓住了。但是抹去TCP timestamps具體是怎麼影響TIME_WAIT狀态socket數目變化的呢?還得具體看看TCP TIME_WAIT快速回收的代碼邏輯。

TCP TIME_WAIT的快速回收

tcp_time_wait的邏輯

Linux kernel不同版本的代碼可能會有小的,這裡根據的是3.10.0版本的代碼。TCP進入TIME_WAIT(FIN_WAIT_2)狀态的邏輯在tcp_minisocks.c中的

tcp_time_wait()

中。

為何用戶端突然出現大量TIME_WAIT堆積梳理問題排查TCP TIME_WAIT的快速回收總結

可以看到這裡有個recycle_ok的bool變量,它确定了處于TIME_WAIT狀态的socket是否被快速recycle。而隻有當tcp_death_row.sysctl_tw_recycle和tp->rx_opt.ts_recent_stamp都為true時,recycle_ok才有機會被置為true。

前面提到過一個不嚴謹的表述:“如果TCP核心參數tcp_tw_recycle和tcp_timestamps同時為1,正常情況下處于TIME_WAIT狀态的socket會被快速回收“,不嚴謹的原因在于:是否recycle看的是tcp_sock中的rx_opt.ts_recent_stamp,而非目前系統的TCP核心參數設定。這裡從SLB的回包中顯然已經沒有TCP timestamps的資訊了,是以這裡recycle_ok隻能是false。後面的邏輯如下:

為何用戶端突然出現大量TIME_WAIT堆積梳理問題排查TCP TIME_WAIT的快速回收總結

了解回收時間和RTO

關于TIME_WAIT的具體回收時間,就在上面這段代碼中:

  • recycle_ok為true時,TIME_WAIT回收的時間是rto;
  • 而recycle_ok為false時,回收時間是正常的2MSL:TCP_TIMEWAIT_LEN (60s,在tcp.h中寫死,核心編譯後固定,不可調)。

上面代碼中的rto隻是個本地變量,它在函數中指派為3.5倍的icsk->icsk_rto (RTO, Retrasmission Timeout):

const int rto = (icsk->icsk_rto << 2) - (icsk->icsk_rto >> 1);           

icsk->icsk_rto根據實際網絡情況動态計算而成,可參考

文章

中的具體描述。在

tcp.h

中規定了RTO的最大和最小值,如下:

#define TCP_RTO_MAX    ((unsigned)(120*HZ))
#define TCP_RTO_MIN    ((unsigned)(HZ/5))           

HZ為1s,TCP_RTO_MAX = 120s, TCP_RTO_MIN = 200ms。是以在區域網路内網環境下,即使RTT小于1ms,TCP逾時重傳的RTO最小值也隻能是200ms。

是以在内網環境中,可以了解成TIME_WAIT socket快速回收的時間為3.5*200ms = 700ms。而對于極端糟糕的網絡環境,這裡可能有個坑,TIME_WAIT“快速回收”的時間可能大于正常回收的60s。

結論

在這個案例中,因為TIME_WAIT的回收時間從3.5倍RTO時間變成了60秒,并且用戶端有比較大的TCP短連接配接并發,是以導緻了用戶端迅速堆積處于TIME_WAIT狀态的socket。

總結

本文中提到的是一個7層SLB的案例,但實際上這個抹去TCP timestamp的行為會發生在full nat模式的LVS中,是以以full nat模式LVS作為vip的産品都有可能會出現這個問題。

如何解決

對于沒有TCP timestamp資訊的用戶端來說,要讓這麼多的TIME_WAIT socket迅速消失,比較優雅的方法是使用TCP長連接配接來代替短連接配接。

如何了解

換一個角度,為什麼要解決呢?在用戶端出現4000多個TIME_WAIT是不是個問題呢?其實在正常系統環境中算不上問題,隻是可能令你的netstat/ss輸出比較不友好而已。TIME_WAIT socket對于系統資源的消耗影響非常小,而真正需要考慮因為TIME_WAIT多而觸碰到限制的是如下幾個方面:

  • 源端口數量 (net.ipv4.ip_local_port_range)
  • TIME_WAIT bucket 數量 (net.ipv4.tcp_max_tw_buckets)
  • 檔案描述符數量 (max open files)

如果TIME_WAIT數量離上面這些limit還比較遠,那我們可以安安心心地讓子彈再飛一會。

繼續閱讀