天天看點

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

根據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套接字拖死。      

繼續閱讀