上篇筆記主要介紹了與TIME_WAIT相關的基礎知識,本文則從實踐出發,說明如何解決文章標題提出的問題。
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問題
================= EOF ==================