http://blog.chinaunix.net/uid-29075379-id-3904985.html
這個問題在網上已經有很多人討論過了,再談這個問題,只是根據我處理過的相關業務來談談我的看法。至於什麼是TIMEWAIT,我想,並不需要多說。
TIMEWAIT狀態本身和應用層的客戶端或者伺服器是沒有關係的。僅僅是主動關閉的一方,在使用FIN|ACK|FIN|ACK四分組正常關閉TCP連線的時候會出現這個TIMEWAIT。伺服器在處理客戶端請求的時候,如果你的程式設計為伺服器主動關閉,那麼你才有可能需要關注這個TIMEWAIT狀態過多的問題。如果你的伺服器設計為被動關閉,那麼你首先要關注的是CLOSE_WAIT。
原則
TIMEWAIT並不是多餘的。在TCP協議被創造,經歷了大量的實際場景實踐之後,TIMEWAIT出現了,因為TCP主動關閉連線的一方需要TIMEWAIT狀態,它是我們的朋友。這是《UNIX網路程式設計》的作者----Steven對TIMEWAIT的態度。
TIMEWAIT是友好的
TCP要保證在所有可能的情況下使得所有的資料都能夠被正確送達。當你關閉一個socket時,主動關閉一端的socket將進入TIME_WAIT狀態,而被動關閉一方則轉入CLOSED狀態,這的確能夠保證所有的資料都被傳輸。當一個socket關閉的時候,是通過兩端四次握手完成的,當一端呼叫close()時,就說明本端沒有資料要傳送了。這好似看來在握手完成以後,socket就都可以處於初始的CLOSED狀態了,其實不然。原因是這樣安排狀態有兩個問題, 首先,我們沒有任何機制保證最後的一個ACK能夠正常傳輸,第二,網路上仍然有可能有殘餘的資料包(wandering duplicates),我們也必須能夠正常處理。
TIMEWAIT就是為了解決這兩個問題而生的。
1.假設最後一個ACK丟失了,被動關閉一方會重發它的FIN。主動關閉一方必須維持一個有效狀態資訊(TIMEWAIT狀態下維持),以便能夠重發ACK。如果主動關閉的socket不維持這種狀態而進入CLOSED狀態,那麼主動關閉的socket在處於CLOSED狀態時,接收到FIN後將會響應一個RST。被動關閉一方接收到RST後會認為出錯了。如果TCP協議想要正常完成必要的操作而終止雙方的資料流傳輸,就必須完全正確的傳輸四次握手的四個節,不能有任何的丟失。這就是為什麼socket在關閉後,仍然處於TIME_WAIT狀態的第一個原因,因為他要等待以便重發ACK。
2.假設目前連線的通訊雙方都已經呼叫了close(),雙方同時進入CLOSED的終結狀態,而沒有走TIME_WAIT狀態。會出現如下問題,現在有一個新的連線被建立起來,使用的IP地址與埠與先前的完全相同,後建立的連線是原先連線的一個完全複用。還假定原先的連線中有資料包殘存於網路之中,這樣新的連線收到的資料包中有可能是先前連線的資料包。為了防止這一點,TCP不允許新連線複用TIME_WAIT狀態下的socket。處於TIME_WAIT狀態的socket在等待兩倍的MSL時間以後(之所以是兩倍的MSL,是由於MSL是一個資料包在網路中單向發出到認定丟失的時間,一個資料包有可能在傳送途中或是其響應過程中成為殘餘資料包,確認一個資料包及其響應的丟棄的需要兩倍的MSL),將會轉變為CLOSED狀態。這就意味著,一個成功建立的連線,必然使得先前網路中殘餘的資料包都丟失了。
大量TIMEWAIT在某些場景中導致的令人頭疼的業務問題
大量TIMEWAIT出現,並且需要解決的場景
在高併發短連線的TCP伺服器上,當伺服器處理完請求後立刻按照主動正常關閉連線。。。這個場景下,會出現大量socket處於TIMEWAIT狀態。如果客戶端的併發量持續很高,此時部分客戶端就會顯示連線不上。
我來解釋下這個場景。主動正常關閉TCP連線,都會出現TIMEWAIT。為什麼我們要關注這個高併發短連線呢?以下方面需要注意:
在這個場景中,短連線表示“業務處理+傳輸資料的時間 遠遠小於 TIMEWAIT超時的時間”的連線。這裡有個相對長短的概念,比如,取一個web頁面,1秒鐘的http短連線處理完業務,在關閉連線之後,這個業務用過的埠會停留在TIMEWAIT狀態幾分鐘,而這幾分鐘,其他HTTP請求來臨的時候是無法佔用此埠的。單用這個業務計算伺服器的利用率會發現,伺服器幹正經事的時間和埠(資源)被掛著無法被使用的時間的比例是 1:幾百,伺服器資源嚴重浪費。(說個題外話,從這個意義出發來考慮伺服器效能調優的話,長連線業務的服務就不需要考慮TIMEWAIT狀態。同時,假如你對伺服器業務場景非常熟悉,你會發現,在實際業務場景中,一般長連線對應的業務的併發量並不會很高)
綜合以上方面,持續的到達一定量的高併發短連線,會使伺服器因埠資源不足而拒絕為一部分客戶服務。同時,這些埠都是伺服器臨時分配,無法用SO_REUSEADDR選項解決這個問題:(
一對矛盾
TIMEWAIT既友好,又令人頭疼。
但是我們還是要抱著一個友好的態度來看待它,因為它盡它的能力保證了伺服器的健壯性。
可行而且必須存在,但是不符合原則的解決方式
1. linux沒有在sysctl或者proc檔案系統暴露修改這個TIMEWAIT超時時間的介面,可以修改核心協議棧程式碼中關於這個TIMEWAIT的超時時間引數,重編核心,讓它縮短超時時間,加快回收;
2. 利用SO_LINGER選項的強制關閉方式,發RST而不是FIN,來越過TIMEWAIT狀態,直接進入CLOSED狀態。詳見我的博文《TCP之選項SO_LINGER》。
我如何看待這個問題
為什麼說上述兩種解決方式我覺得可行,但是不符合原則?
我首先認為,我要依靠TIMEWAIT狀態來保證我的伺服器程式健壯,網路上發生的亂七八糟的問題太多了,我先要服務功能正常。
那是不是就不要效能了呢?並不是。如果伺服器上跑的短連線業務量到了我真的必須處理這個TIMEWAIT狀態過多的問題的時候,我的原則是儘量處理,而不是跟TIMEWAIT幹上,非先除之而後快:)如果儘量處理了,還是解決不了問題,仍然拒絕服務部分請求,那我會採取分機器的方法,讓多臺機器來抗這些高併發的短請求。持續十萬併發的短連線請求,兩臺機器,每臺5萬個,應該夠用了吧。一般的業務量以及國內大部分網站其實並不需要關注這個問題,一句話,達不到需要關注這個問題的訪問量。
真正地必須使用上述我認為不合理的方式來解決這個問題的場景有沒有呢?答案是有。
像淘寶、百度、新浪、京東商城這樣的站點,由於有很多靜態小圖片業務,如果過度分服會導致需要上線大量機器,多買機器多花錢,得多配機房,多配備運維工程師來守護這些機器,成本增長非常嚴重。。。這個時候就要盡一切可能去優化。
題外話,伺服器上的技術問題沒有絕對,一切都是為業務需求服務的。
如何儘量處理TIMEWAIT過多
sysctl改兩個核心引數就行了,如下:
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
簡單來說,就是開啟系統的TIMEWAIT重用和快速回收,至於怎麼重用和快速回收,這個問題我沒有深究,實際場景中這麼做確實有效果。用netstat或者ss觀察就能得出結論。
還有些朋友同時也會開啟syncookies這個功能,如下:
net.ipv4.tcp_syncookies = 1
開啟這個syncookies的目的實際上是:“在伺服器資源(並非單指埠資源,拒絕服務有很多種資源不足的情況)不足的情況下,儘量不要拒絕TCP的syn(連線)請求,儘量把syn請求快取起來,留著過會兒有能力的時候處理這些TCP的連線請求”。
如果併發量真的非常非常高,開啟這個其實用處不大。