【Linux網路程式設計筆記】TCP短連線產生大量TIME_WAIT導致無法對外建立新TCP連線的原因及解決方法—實踐篇

hai0808發表於2017-12-19

1. 檢視系統網路配置和當前TCP狀態
        在定位並處理應用程式出現的網路問題時,瞭解系統預設網路配置是非常必要的。以x86_64平臺Linux kernelversion 2.6.9的機器為例,ipv4網路協議的預設配置可以在/proc/sys/net/ipv4/下檢視,其中與TCP協議棧相關的配置項均以tcp_xxx命名,關於這些配置項的含義,請參考這裡的文件,此外,還可以檢視linux原始碼樹中提供的官方文件(src/linux/Documentation/ip-sysctl.txt)。下面列出我機器上幾個需重點關注的配置項及其預設值:

cat /proc/sys/net/ipv4/ip_local_port_range      32768   61000
cat /proc/sys/net/ipv4/tcp_max_syn_backlog      1024
cat /proc/sys/net/ipv4/tcp_syn_retries          5
cat /proc/sys/net/ipv4/tcp_max_tw_buckets       180000
cat /proc/sys/net/ipv4/tcp_tw_recycle           0
cat /proc/sys/net/ipv4/tcp_tw_reuse             0

        其中,前3項分別說明了local port的分配範圍(預設的可用埠數不到3w)、incomplete connection queue的最大長度以及3次握手時SYN的最大重試次數,這3項配置的含義,有個概念即可。後3項配置的含義則需要理解,因為它們在定位、解決問題過程中要用到,下面進行重點說明。
        1) tcp_max_tw_buckets
         這篇文件 是這樣描述的:Maximal number of time wait sockets held by system simultaneously. If this number is exceeded TIME_WAIT socket is immediately destroyed and warning is printed. This limit exists only to prevent simple DoS attacks, you must not lower the limit artificially, but rather increase it (probably, after increasing installed memory), if network conditions require more than default value (180000).
        可見,該配置項用來防範簡單的DoS攻擊 ,在某些情況下,可以適當調大,但絕對不應調小,否則,後果自負。。。
         2) tcp_tw_recycle
        Enable fast recycling of sockets in TIME-WAIT status. The defaultvalue is 0 (disabled). It should not be changed without advice/request of technical experts.
        該配置項可用於快速回收處於TIME_WAIT狀態的socket以便重新分配。預設是關閉的,必要時可以開啟該配置。但是開啟該配置項後,有一些需要注意的地方,本文後面會提到。
         3) tcp_tw_reuse
         Allow to reuse TIME-WAIT sockets for new connections when it is safe from protocol viewpoint. The default value is 0. It should not
be changed without advice/request of technical experts.
        開啟該選項後,kernel會複用處於TIME_WAIT狀態的socket,當然複用的前提是“從協議角度來看,複用是安全的”。關於“ 在什麼情況下,協議認為複用是安全的 ”這個問題,這篇文章 從linux kernel原始碼中挖出了答案,感興趣的同學可以檢視。

 2. 網路問題定位思路
        參考前篇筆記開始處描述的線上實際問題,收到某臺機器無法對外建立新連線的報警時,排查定位問題過程如下:
       用netstat –at | grep “TIME_WAIT”統計發現,當時出問題的那臺機器上共有10w+處於TIME_WAIT狀態的TCP連線,進一步分析發現,由報警模組引起的TIME_WAIT連線有2w+。將netstat輸出的統計結果重定位到檔案中繼續分析,一般會看到本機的port被大量佔用。
        由本文前面介紹的系統配置項可知,tcp_max_tw_buckets預設值為18w,而ip_local_port_range範圍不到3w,大量的TIME_WAIT狀態使得local port在TIME_WAIT持續期間不能被再次分配,即沒有可用的local port,這將是導致新建連線失敗的最大原因
        在這裡提醒大家:上面的結論只是我們的初步判斷,具體原因還需要根據程式碼的異常返回值(如socket api的返回值及errno等)和模組日誌做進一步確認。無法建立新連線的原因還可能是被其它模組列入黑名單了,本人就有過這方面的教訓:程式中用libcurl api請求下游模組失敗,初步定位發現機器TIME_WAIT狀態很多,於是沒仔細分析curl輸出日誌就認為是TIME_WAIT引起的問題,導致浪費了很多時間,折騰了半天發現不對勁後才想起,下游模組有防攻擊機制,而發起請求的機器ip被不在下游模組的訪問白名單內,高峰期上游模組通過curl請求下游的次數太過頻繁被列入黑名單,新建連線時被下游模組的TCP層直接以RST包斷開連線,導致curl api返回”Recv failure: Connection reset by peer”的錯誤。慘痛的教訓呀 =_=
       另外,關於何時傳送RST包,《Unix Network Programming Volume 1》第4.3節做了說明,作為筆記,摘出如下:
       An RST is a type of TCP segment that is sent by TCP when somethingis wrong.Three conditions that generatean RST are:            
        1) when a SYN arrives for a port that has no listening server;
        2) when TCP wants to abort an existing connection;
        3) when TCP receives a segment for a connection that does not exist. (TCPv1 [pp.246–250] contains additional information.)

3. 解決方法
        可以用兩種思路來解決機器TIME_WAIT過多導致無法對外建立新TCP連線的問題。
        3.1 修改系統配置
        具體來說,需要修改本文前面介紹的tcp_max_tw_buckets、tcp_tw_recycle、tcp_tw_reuse這三個配置項。
        1)將tcp_max_tw_buckets調大,從本文第一部分可知,其預設值為18w(不同核心可能有所不同,需以機器實際配置為準),根據文件,我們可以適當調大,至於上限是多少,文件沒有給出說明,我也不清楚。個人認為這種方法只能對TIME_WAIT過多的問題起到緩解作用,隨著訪問壓力的持續,該出現的問題遲早還是會出現,治標不治本。
        2)開啟tcp_tw_recycle選項:在shell終端輸入命令”echo 1 > /proc/sys/net/ipv4/tcp_tw_recycle”可以開啟該配置。
        需要明確的是:其實TIME_WAIT狀態的socket是否被快速回收是由tcp_tw_recycle和tcp_timestamps兩個配置項共同決定的,只不過由於tcp_timestamps預設就是開啟的,故大多數文章只提到設定tcp_tw_recycle為1。更詳細的說明(分析kernel原始碼)可參見這篇文章
        還需要特別注意的是:當client與server之間有如NAT這類網路轉換裝置時,開啟tcp_tw_recycle選項可能會導致server端drop(直接傳送RST)來自client的SYN包。具體的案例及原因分析,可以參考這裡這裡這裡以及這裡的分析,本文不再贅述。
        3)開啟tcp_tw_reuse選項:echo1 > /proc/sys/net/ipv4/tcp_tw_reuse。該選項也是與tcp_timestamps共同起作用的,另外socket reuse也是有條件的,具體說明請參見這篇文章。查了很多資料,與在用到NAT或FireWall的網路環境下開啟tcp_tw_recycle後可能帶來副作用相比,貌似沒有發現tcp_tw_reuse引起的網路問題。
        3.2 修改應用程式
       具體來說,可以細分為兩種方式:
        1)將TCP短連線改造為長連線。通常情況下,如果發起連線的目標也是自己可控制的伺服器時,它們自己的TCP通訊最好採用長連線,避免大量TCP短連線每次建立/釋放產生的各種開銷;如果建立連線的目標是不受自己控制的機器時,能否使用長連線就需要考慮對方機器是否支援長連線方式了。
        2)通過getsockopt/setsockoptapi設定socket的SO_LINGER選項,關於SO_LINGER選項的設定方法,《UNP Volume1》一書7.5節給出了詳細說明,想深入理解的同學可以去查閱該教材,也可以參考這篇文章,講的還算清楚。

4. 需要補充說明的問題
        我們說TIME_WAIT過多可能引起無法對外建立新連線,其實有一個例外但比較常見的情況:S模組作為WebServer部署在伺服器上,繫結本地某個埠;客戶端與S間為短連線,每次互動完成後由S主動斷開連線。這樣,當客戶端併發訪問次數很高時,S模組所在的機器可能會有大量處於TIME_WAIT狀態的TCP連線。但由於伺服器模組繫結了埠,故在這種情況下,並不會引起“由於TIME_WAIT過多導致無法建立新連線”的問題。也就是說,本文討論的情況,通常只會在每次由作業系統分配隨機埠的程式執行的機器上出現(每次分配隨機埠,導致後面無埠可用)。

【參考資料】
1. ipsysctl-tutorial之tcpvariables
2. proc_sys_net_ipv4 
3. linux+v3.2.8/Documentation/networking/ip-sysctl.txt
4. 系列文章—tcp短連線TIME_WAIT問題解決方法大全1-5
5. 開啟tcp_tw_recycle引起的一個問題
6. dropping of connections with tcp_tw_recycle = 1
7. tcp_tw_recycle和nat造成syn_ack問題

相關文章