tcp短鏈接TIME_WAIT問題解決方法大全

tcp鏈接是網絡編程中最基礎的概念,基於不一樣的使用場景,咱們通常區分爲「長鏈接」和「短鏈接」,
長短鏈接的優勢和缺點這裏就不詳細展開了,有心的同窗直接去google查詢,本文主要關注如何解決tcp短鏈接的TIME_WAIT問題。


短鏈接最大的優勢是方便,特別是腳本語言,因爲執行完畢後腳本語言的進程就結束了,基本上都是用短鏈接。
但短鏈接最大的缺點是將佔用大量的系統資源,例如:本地端口、socket句柄。
致使這個問題的緣由其實很簡單:tcp協議層並無長短鏈接的概念,所以無論長鏈接仍是短鏈接,鏈接創建->數據傳輸->鏈接關閉的流程和處理都是同樣的。


正常的TCP客戶端鏈接在關閉後,會進入一個TIME_WAIT的狀態,持續的時間通常在1~4分鐘,對於鏈接數不高的場景,1~4分鐘其實並不長,對系統也不會有什麼影響,
但若是短期內(例如1s內)進行大量的短鏈接,則可能出現這樣一種狀況:客戶端所在的操做系統的socket端口和句柄被用盡,系統沒法再發起新的鏈接!


舉例來講:假設每秒創建了1000個短鏈接(Web場景下是很常見的,例如每一個請求都去訪問memcached),假設TIME_WAIT的時間是1分鐘,則1分鐘內須要創建6W個短鏈接,
因爲TIME_WAIT時間是1分鐘,這些短鏈接1分鐘內都處於TIME_WAIT狀態,都不會釋放,而Linux默認的本地端口範圍配置是:net.ipv4.ip_local_port_range = 32768    61000
不到3W,所以這種狀況下新的請求因爲沒有本地端口就不能創建了。


能夠經過以下方式來解決這個問題:
1)能夠改成長鏈接,但代價較大,長鏈接太多會致使服務器性能問題,並且PHP等腳本語言,須要經過proxy之類的軟件才能實現長鏈接;
2)修改ipv4.ip_local_port_range,增大可用端口範圍,但只能緩解問題,不能根本解決問題;
3)客戶端程序中設置socket的SO_LINGER選項;
4)客戶端機器打開tcp_tw_recycle和tcp_timestamps選項;
5)客戶端機器打開tcp_tw_reuse和tcp_timestamps選項;
6)客戶端機器設置tcp_max_tw_buckets爲一個很小的值;


在解決php鏈接Memcached的短鏈接問題過程當中,咱們主要驗證了3)4)5)6)幾種方法,採起的是基本功能驗證和代碼驗證,並無進行性能壓力測試驗證,
所以實際應用的時候須要注意觀察業務運行狀況,發現丟包、斷連、沒法鏈接等現象時,須要關注是不是由於這些選項致使的


雖然這幾種方法均可以經過google查詢到相關信息,但這些信息大部分都是泛泛而談,並且絕大部分都是人云亦云,沒有很大參考價值。
咱們在定位和處理這些問題過程當中,遇到一些疑惑和困難,也花費了一些時間去定位和解決,如下就是相關的經驗總結。php

 

 

SO_LINGER是一個socket選項,經過setsockopt API進行設置,使用起來比較簡單,但其實現機制比較複雜,且字面意思上比較難理解。
解釋最清楚的當屬《Unix網絡編程卷1》中的說明(7.5章節),這裏簡單摘錄:
SO_LINGER的值用以下數據結構表示:
struct linger {
     int l_onoff; /* 0 = off, nozero = on */
     int l_linger; /* linger time */linux

};編程

 

其取值和處理以下:
一、設置 l_onoff爲0,則該選項關閉,l_linger的值被忽略,等於內核缺省狀況,close調用會當即返回給調用者,若是可能將會傳輸任何未發送的數據;
二、設置 l_onoff爲非0,l_linger爲0,則套接口關閉時TCP夭折鏈接,TCP將丟棄保留在套接口發送緩衝區中的任何數據併發送一個RST給對方,
   而不是一般的四分組終止序列,這避免了TIME_WAIT狀態;
三、設置 l_onoff 爲非0,l_linger爲非0,當套接口關閉時內核將拖延一段時間(由l_linger決定)。
   若是套接口緩衝區中仍殘留數據,進程將處於睡眠狀態,直 到(a)全部數據發送完且被對方確認,以後進行正常的終止序列(描述字訪問計數爲0)
   或(b)延遲時間到。此種狀況下,應用程序檢查close的返回值是很是重要的,若是在數據發送完並被確認前時間到,close將返回EWOULDBLOCK錯誤且套接口發送緩衝區中的任何數據都丟失。
   close的成功返回僅告訴咱們發送的數據(和FIN)已由對方TCP確認,它並不能告訴咱們對方應用進程是否已讀了數據。若是套接口設爲非阻塞的,它將不等待close完成。
   
第一種狀況其實和不設置沒有區別,第二種狀況能夠用於避免TIME_WAIT狀態,但在Linux上測試的時候,並未發現發送了RST選項,而是正常進行了四步關閉流程,
初步推斷是「只有在丟棄數據的時候才發送RST」,若是沒有丟棄數據,則走正常的關閉流程。
查看Linux源碼,確實有這麼一段註釋和源碼:
=====linux-2.6.37 net/ipv4/tcp.c 1915=====
/* As outlined in RFC 2525, section 2.17, we send a RST here because
* data was lost. To witness the awful effects of the old behavior of
* always doing a FIN, run an older 2.1.x kernel or 2.0.x, start a bulk
* GET in an FTP client, suspend the process, wait for the client to
* advertise a zero window, then kill -9 the FTP client, wheee...
* Note: timeout is always zero in such a case.
*/
if (data_was_unread) {
/* Unread data was tossed, zap the connection. */
NET_INC_STATS_USER(sock_net(sk), LINUX_MIB_TCPABORTONCLOSE);
tcp_set_state(sk, TCP_CLOSE);
tcp_send_active_reset(sk, sk->sk_allocation);

另外,從原理上來講,這個選項有必定的危險性,可能致使丟數據,使用的時候要當心一些,但咱們在實測libmemcached的過程當中,沒有發現此類現象,
應該是和libmemcached的通信協議設置有關,也多是咱們的壓力不夠大,不會出現這種狀況。


第三種狀況其實就是第一種和第二種的折中處理,且當socket爲非阻塞的場景下是沒有做用的。
對於應對短鏈接致使的大量TIME_WAIT鏈接問題,我的認爲第二種處理是最優的選擇,libmemcached就是採用這種方式,
從實測狀況來看,打開這個選項後,TIME_WAIT鏈接數爲0,且不受網絡組網(例如是否虛擬機等)的影響。服務器

相關文章
相關標籤/搜索