我們經常會遇到在服務器上看到大量的TIME_WAIT,它們占用進程不釋放,最后會導致所有進程數被耗完,服務器負載增高等生產事故,具體是什么原因導致的呢?我們先來看看TCP的三次握手四次揮手都是怎樣的一個過程。
TCP三次握手
三次握手的過程如下圖:
具體的過程如下:
(1)、客戶端主動發起連接,向服務端監聽的端口發送SYN包和一個隨機的序列seq到服務端;
(2)、服務端收到SYN包后,回復客戶端,發送ACK包(收到的seq加1)和一個隨機序列到客戶端;
(3)、客戶端收到ACK包后,回復服務端,發送一個ACK包(收到的seq加1);
由上圖可以看到具體的狀態變化,值得注意的是SYN_RECV狀態,有時候我們會發現服務器上有大量的SYN_RECV狀態,看到這個一般就可以確定是著名的SYN泛洪攻擊,它就是用大量的偽終端來連接服務端,服務端回復ACK(也就是步驟2),由於這些終端都不存在,所以就會在第二部卡住了,造成服務端有大量的SYN_RECV狀態,不斷的消耗TCP進程數,導致正常業務無法建立正常連接。詳細過程請移步至百度百科。
TCP四次揮手
四次揮手的過程如下圖:
具體過程如下:
(1)、客戶端主動發起斷開連接,發送FIN包到服務端;
(2)、服務端收到FIN包后回復ACK包給客戶端;
(3)、服務端發起斷開連接,發送FIN包到客戶端;
(4)、客戶端收到FIN包后回復ACK給服務端;
具體過程還是比較簡單,詳情可以由上圖可知。這里要注意三個狀態:CLOSE_WAIT、TIME_WAIT和FIN_WAIT2。
其中CLOSE_WAIT是出現在被動斷開方,如上圖就是服務端。如果服務端出現大量的CLOSE_WAIT狀態,一般情況下是由於程序中沒有正常調用close()關閉連接,所以出現這個問題一般是會結合開發一起找原因。
FIN_WAIT2狀態會等待服務端發送SYN斷開連接,如果服務端一直沒有發送斷開,客戶端會等待tcp_fin_timeout時間斷開socket連接,如果有大量的FIN_WAIT2狀態,就要檢查服務端的應用程序是否調用close()關閉連接,如果一時查不出原因,可以修改內核參數tcp_fin_timeout的時間,比如:net.ipv4.tcp_fin_timeout=30。
而對於TIME_WAIT,它只會出現在主動斷開方,也就是上圖中的客戶端。之所以有TIME_WAIT這個狀態而不是直接編程CLOSED狀態主要有以下兩個原因:
(1)、保證服務端能夠正常收到最后一個ACK包,確保是正常斷開,可靠釋放;
(2)、防止數據錯亂。假如沒有這個TIME_WAIT狀態,假設當前有一條連接,因某些原因先關閉,緊接着又以相同的四元組建立一條新的連接,由於TCP無法區分這兩條連接有什么不同,就可能會發生這樣的情況:發送數據可能會發送到上一條連接中,這就會出現一些數據混亂的問題。
TIME_WAIT狀態有一個默認過期時間,默認是2MSL(最大生存時間),不同的操作系統默認的MSL是不一樣的。如果有大量的TIME_WAIT,就會造成本地端口不釋放,無法通過這個端口建立新的連接,如果本地端口都用完了,就會出現無法建立TCP連接來訪問服務端了。
解決方法一般有兩種(具體需要根據自身情況來定):
1、調節內核參數,以CentOS為例,主要的參數有tcp_max_tw_buckets、tcp_tw_recycle、tcp_tw_reuse這三個配置項。
(1)、tcp_max_tw_buckets:該配置項用來防范簡單的DoS攻擊 ,在某些情況下,可以適當調大,但絕對不應調小。
(2)、tcp_tw_recycle:該配置項可用於快速回收處於TIME_WAIT狀態的socket以便重新分配。默認是關閉的,必要時可以開啟該配置。
(3)、tcp_tw_reuse:開啟該選項后,kernel會復用處於TIME_WAIT狀態的socket,當然復用的前提是“從協議角度來看,復用是安全的”。
2、修改應用程序。
(1)、將TCP短連接改造為長連接。通常情況下,如果發起連接的目標也是自己可控制的服務器時,它們自己的TCP通信最好采用長連接,避免大量TCP短連接每次建立/釋放產生的各種開銷;如果建立連接的目標是不受自己控制的機器時,能否使用長連接就需要考慮對方機器是否支持長連接方式了。
(2)、通過getsockopt/setsockoptapi設置socket的SO_LINGER選項。