雲計算

為何客戶端突然出現大量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。

image.png

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

image.png

排查到這裡,我們差不多把這個變化的因素抓住了。但是抹去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()中。

image.png

可以看到這裡有個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。後面的邏輯如下:

image.png

理解回收時間和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還比較遠,那我們可以安安心心地讓子彈再飛一會。

Leave a Reply

Your email address will not be published. Required fields are marked *