linux中連接數過多(TIME_WAIT/CLOSE_WAIT)讀這一篇就夠了

參考:https://www.phpmianshi.com/?id=106

根據TCP/IP介紹,socket大概包含10個連接狀態。我們平常工作中遇到的,除了針對SYN的拒絕服務攻擊,如果有異常,大概率是TIME_WAIT和CLOSE_WAIT的問題。
TIME_WAIT一般通過優化內核參數能夠解決;CLOSE_WAIT一般是由於程序編寫不合理造成的,更應該引起開發者注意。

TIME_WAIT

TIME_WAIT是主動關閉連接的一方保持的狀態,像nginx、爬蟲服務器,經常發生大量處於time_wait狀態的連接。TCP一般在主動關閉連接後,會等待2MS,然後徹底關閉連接。由於HTTP使用了TCP協議,所以在這些頻繁開關連接的服務器上,就積壓了非常多的TIME_WAIT狀態連接。

某些系統通過dmesg可以看到以下信息。

__ratelimit: 2170 callbacks suppressed
TCP: time wait bucket table overflow
TCP: time wait bucket table overflow
TCP: time wait bucket table overflow
TCP: time wait bucket table overflow

通過ss -s命令查看,可以看到timewait已經有2w個了。

ss -s
Total: 174 (kernel 199)
TCP:   20047 (estab 32, closed 20000, orphaned 4, synrecv 0, timewait 20000/0), ports 10785

sysctl命令可以設置這些參數,如果想要重啓生效的話,加入/etc/sysctl.conf文件中。

# 修改閾值
net.ipv4.tcp_max_tw_buckets = 50000 
# 表示開啓TCP連接中TIME-WAIT sockets的快速回收
net.ipv4.tcp_tw_reuse = 1
#啓用timewait 快速回收。這個一定要開啓,默認是關閉的。
net.ipv4.tcp_tw_recycle= 1   
# 修改系統默認的TIMEOUT時間,默認是60s
net.ipv4.tcp_fin_timeout = 10

測試參數的話,可以使用 sysctl -w net.ipv4.tcp_tw_reuse = 1 這樣的命令。如果是寫入進文件的,則使用sysctl -p生效。

CLOSE_WAIT

CLOSE_WAIT一般是由於對端主動關閉,而我方沒有正確處理的原因引起的。說白了,就是程序寫的有問題,屬於危害比較大的一種。

Socket中的11種狀態

1、客戶端獨有的:(1)SYN_SENT (2)FIN_WAIT1 (3)FIN_WAIT2 (4)CLOSING (5)TIME_WAIT 。

2、服務器獨有的:(1)LISTEN (2)SYN_RCVD (3)CLOSE_WAIT (4)LAST_ACK 。

3、共有的:(1)CLOSED (2)ESTABLISHED 。


各個狀態的意義如下:

LISTEN - 偵聽來自遠方TCP端口的連接請求;

SYN-SENT -在發送連接請求後等待匹配的連接請求;

SYN-RECEIVED- 在收到和發送一個連接請求後等待對連接請求的確認;

ESTABLISHED- 代表一個打開的連接,數據可以傳送給用戶;

FIN-WAIT-1 - 等待遠程TCP的連接中斷請求,或先前的連接中斷請求的確認;

FIN-WAIT-2 - 從遠程TCP等待連接中斷請求;

CLOSE-WAIT - 等待從本地用戶發來的連接中斷請求;

CLOSING -等待遠程TCP對連接中斷的確認;

LAST-ACK - 等待原來發向遠程TCP的連接中斷請求的確認;

TIME-WAIT -等待足夠的時間以確保遠程TCP接收到連接中斷請求的確認;

CLOSED - 沒有任何連接狀態;

 

我們平常工作中遇到的,除了針對SYN的拒絕服務攻擊,大概率是TIME_WAIT和CLOSE_WAIT的問題。

TIME_WAIT一般通過優化內核參數能夠解決。

CLOSE_WAIT一般是由於程序編寫不合理造成的,更應該引起開發者注意。

 

1. time_wait狀態如何產生? 
調用close()發起主動關閉的一方,在發送最後一個ACK之後會進入time_wait的狀態,也就說該發送方會保持2MSL時間之後纔會回到初始狀態。MSL值得是數據包在網絡中的最大生存時間。產生這種結果使得這個TCP連接在2MSL連接等待期間,定義這個連接的四元組(客戶端IP地址和端口,服務端IP地址和端口號)不能被使用。

2.time_wait狀態產生的原因

1)爲實現TCP全雙工連接的可靠釋放

假設發起主動關閉的一方(client)最後發送的ACK在網絡中丟失,由於TCP協議的重傳機制,執行被動關閉的一方(server)將會重發其FIN,在該FIN到達client之前,client必須維護這條連接狀態,也就說這條TCP連接所對應的資源(client方的local_ip,local_port)不能被立即釋放或重新分配,直到另一方重發的FIN達到之後,client重發ACK後,經過2MSL時間週期沒有再收到另一方的FIN之後,該TCP連接才能恢復初始的CLOSED狀態。如果主動關閉一方不維護這樣一個TIME_WAIT狀態,那麼當被動關閉一方重發的FIN到達時,主動關閉一方的TCP傳輸層會用RST包響應對方,這會被對方認爲是有錯誤發生,然而這事實上只是正常的關閉連接過程,並非異常。

2)爲使舊的數據包在網絡因過期而消失

爲說明這個問題,我們先假設TCP協議中不存在TIME_WAIT狀態的限制,再假設當前有一條TCP連接:(local_ip, local_port, remote_ip,remote_port),因某些原因,我們先關閉,接着很快以相同的四元組建立一條新連接。本文前面介紹過,TCP連接由四元組唯一標識,因此,在我們假設的情況中,TCP協議棧是無法區分前後兩條TCP連接的不同的,在它看來,這根本就是同一條連接,中間先釋放再建立的過程對其來說是“感知”不到的。這樣就可能發生這樣的情況:前一條TCP連接由local peer發送的數據到達remote peer後,會被該remot peer的TCP傳輸層當做當前TCP連接的正常數據接收並向上傳遞至應用層(而事實上,在我們假設的場景下,這些舊數據到達remote peer前,舊連接已斷開且一條由相同四元組構成的新TCP連接已建立,因此,這些舊數據是不應該被向上傳遞至應用層的),從而引起數據錯亂進而導致各種無法預知的詭異現象。作爲一種可靠的傳輸協議,TCP必須在協議層面考慮並避免這種情況的發生,這正是TIME_WAIT狀態存在的第2個原因。

3)總結 
具體而言,local peer主動調用close後,此時的TCP連接進入TIME_WAIT狀態,處於該狀態下的TCP連接不能立即以同樣的四元組建立新連接,即發起active close的那方佔用的local port在TIME_WAIT期間不能再被重新分配。由於TIME_WAIT狀態持續時間爲2MSL,這樣保證了舊TCP連接雙工鏈路中的舊數據包均因過期(超過MSL)而消失,此後,就可以用相同的四元組建立一條新連接而不會發生前後兩次連接數據錯亂的情況。

 

通過ss -s命令查看,可以看到timewait已經有2w個了。

 

如果想要重啓生效的話,加入/etc/sysctl.conf文件中。

net.ipv4.tcp_syncookies = 1  #表示開啓SYN Cookies。當出現SYN等待隊列溢出時,啓用cookies來處理,可防範少量SYN攻擊,默認爲0,表示關閉;
net.ipv4.tcp_max_tw_buckets = 50000 
net.ipv4.tcp_tw_reuse = 1   #允許將TIME-WAIT sockets重新用於新的TCP連接,默認爲0,表示關閉;
net.ipv4.tcp_tw_recycle= 1  #開啓TCP連接中TIME-WAIT sockets的快速回收,默認爲0,表示關閉。
net.ipv4.tcp_fin_timeout = 10  # 修改系統默認的TIMEOUT時間,默認是60s

 

使用sysctl -p生效

 

如果以上配置調優後性能還不理想,可繼續修改一下配置:

vi /etc/sysctl.conf
net.ipv4.tcp_keepalive_time = 1200 #表示當keepalive起用的時候,TCP發送keepalive消息的頻度。缺省是2小時,改爲20分鐘。
net.ipv4.ip_local_port_range = 1024 65000 #表示用於向外連接的端口範圍。缺省情況下很小:32768到61000,改爲1024到65000。
net.ipv4.tcp_max_syn_backlog = 8192 #表示SYN隊列的長度,默認爲1024,加大隊列長度爲8192,可以容納更多等待連接的網絡連接數。
net.ipv4.tcp_max_tw_buckets = 5000  #同時保持TIME_WAIT套接字的最大個數,超過這個數字那麼該TIME_WAIT套接字將立刻被釋放
#並在/var/log/message日誌中打印警告信息(TCP: time wait bucket table overflow)。
#這個過多主要是消耗內存,單個TIME_WAIT佔用內存非常小,但是多了就不好了,這個主要看內存以及你的服務器是否直接對外
#默認爲180000,改爲5000。
#對於Apache、Nginx等服務器,上幾行的參數可以很好地減少TIME_WAIT套接字數量
#但是對於 Squid,效果卻不大。此項參數可以控制TIME_WAIT套接字的最大數量,避免Squid服務器被大量的TIME_WAIT套接字拖死。

 

CLOSE_WAIT
這種狀態的含義其實是表示在等待關閉。怎麼理解呢?當對方close一個SOCKET後發送FIN報文給自己,你係統毫無疑問地會迴應一個ACK報文給對方,此時則進入到CLOSE_WAIT狀態。接下來呢,實際上你真正需要考慮的事情是查看你是否還有數據發送給對方,如果沒有的話,那麼你也就可以close這個SOCKET,發送FIN報文給對方,也即關閉連接。所以你在CLOSE_WAIT狀態下,需要完成的事情是等待你去關閉連接。CLOSE_WAIT一般是由於對端主動關閉,而我方沒有正確處理的原因引起的。說白了,就是程序寫的有問題,屬於危害比較大的一種。代碼需要判斷socket,一旦讀到0,斷開連接,read返回負,檢查一下errno,如果不AGAIN,就斷開連接。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章