【轉】記一次TIME_WAIT網路故障

  • 2019 年 10 月 4 日
  • 筆記

前段時間,組裡遇到個問題:線上某個業務出現了短連接太多,造成tw_bucket溢出。

這個問題比較常見,網上常見的方法有開啟reuse和快速回收(對於NAT結構的網路,慎重開啟快速回收和reuse這2個參數,具體原因可以自行百度)。

出於複習下基礎知識的目的,網上找了篇部落格,

下面是原始博文地址:https://huoding.com/2012/01/19/142  【火丁筆記,裡面介紹了很多調優、故障排查的方法】

直接貼他的博文吧:

—  原文從下面開始

最近發現一個PHP腳本時常出現連不上伺服器的現象,調試了一下,發現是TIME_WAIT狀態過多造成的,本文簡要介紹一下解決問題的過程。

遇到這類問題,我習慣於先用strace命令跟蹤了一下看看:

shell> strace php /path/to/file  EADDRNOTAVAIL (Cannot assign requested address)

從字面結果看似乎是網路資源相關問題。這裡順便介紹一點小技巧:在調試的時候一般是從後往前看strace命令的結果,這樣更容易找到有價值的資訊。

查看一下當前的網路連接情況,結果發現TIME_WAIT數非常大:

shell> netstat -ant | awk '      {++s[$NF]} END {for(k in s) print k,s[k]}  '

補充一下,同netstat相比,ss要快很多:【備註:使用ss -s 命令也可列出概覽,但是不如awk這種顯示優雅 】

shell> ss -ant | awk '      {++s[$1]} END {for(k in s) print k,s[k]}  '

重複了幾次測試,結果每次出問題的時候,TIME_WAIT都等於28233,這真是一個魔法數字!實際原因很簡單,它取決於一個內核參數net.ipv4.ip_local_port_range:

shell> sysctl -a | grep port  net.ipv4.ip_local_port_range = 32768 61000

因為埠範圍是一個閉區間,所以實際可用的埠數量是:

shell> echo $((61000-32768+1))  28233

問題分析到這裡基本就清晰了,解決方向也明確了,內容所限,這裡就不說如何優化程式程式碼了,只是從系統方面來闡述如何解決問題,無非就是以下兩個方面:

首先是增加本地可用埠數量。這點可以用以下命令來實現:

shell> sysctl net.ipv4.ip_local_port_range="10240 61000"

其次是減少TIME_WAIT連接狀態。網路上已經有不少相關的介紹,大多是建議:

shell> sysctl net.ipv4.tcp_tw_reuse=1  shell> sysctl net.ipv4.tcp_tw_recycle=1

註:通過sysctl命令修改內核參數,重啟後會還原,要想持久化可以參考前面的方法。

這兩個選項在降低TIME_WAIT數量方面可以說是立竿見影,不過如果你覺得問題已經完美搞定那就錯了,實際上這樣可能會引入一個更複雜的網路故障。

關於內核參數的詳細介紹,可以參考官方文檔。我們這裡簡要說明一下tcp_tw_recycle參數。它用來快速回收TIME_WAIT連接,不過如果在NAT環境下會引發問題。

RFC1323中有如下一段描述:

An additional mechanism could be added to the TCP, a per-host cache of the last timestamp received from any connection. This value could then be used in the PAWS mechanism to reject old duplicate segments from earlier incarnations of the connection, if the timestamp clock can be guaranteed to have ticked at least once since the old connection was open. This would require that the TIME-WAIT delay plus the RTT together must be at least one tick of the sender』s timestamp clock. Such an extension is not part of the proposal of this RFC.

大概意思是說TCP有一種行為,可以快取每個主機最新的時間戳,後續請求中如果時間戳小於快取的時間戳,即視為無效,相應的數據包會被丟棄。

Linux是否啟用這種行為取決於tcp_timestamps和tcp_tw_recycle,因為tcp_timestamps預設就是開啟的,所以當tcp_tw_recycle被開啟後,實際上這種行為就被激活了,當客戶端或服務端以NAT方式構建的時候就可能出現問題,下面以客戶端NAT為例來說明:

當多個客戶端通過NAT方式聯網並與服務端交互時,服務端看到的是同一個IP,也就是說對服務端而言這些客戶端實際上等同於一個,可惜由於這些客戶端的時間戳可能存在差異,於是乎從服務端的視角看,便可能出現時間戳錯亂的現象,進而直接導致時間戳小的數據包被丟棄。如果發生了此類問題,具體的表現通常是是客戶端明明發送的SYN,但服務端就是不響應ACK,我們可以通過下面命令來確認數據包不斷被丟棄的現象:

shell> netstat -s | grep timestamp  ... packets rejects in established connections because of timestamp

安全起見,通常要禁止tcp_tw_recycle。說到這裡,大家可能會想到另一種解決方案:把tcp_timestamps設置為0,tcp_tw_recycle設置為1,這樣不就可以魚與熊掌兼得了么?可惜一旦關閉了tcp_timestamps,那麼即便打開了tcp_tw_recycle,也沒有效果。

好在我們還有另一個內核參數tcp_max_tw_buckets(一般預設是180000)可用:

shell> sysctl net.ipv4.tcp_max_tw_buckets=100000

通過設置它,系統會將多餘的TIME_WAIT刪除掉,此時系統日誌里可能會顯示:「TCP: time wait bucket table overflow」,不過除非不得已,否則不要輕易使用。

總體來說,這次網路故障本身並沒什麼高深之處,本不想羅羅嗦嗦寫這麼多,不過拔出蘿蔔帶出泥,在過程中牽扯的方方面面還是值得大家品味的,於是便有了這篇文字。