問題
某使用者使用如下指令上傳目錄到OSS,中間隐去bucket name。
./ossutil cp oss://<xxxx>/img/330802010400071904 -r -j 15 /home/oss/vis-test/
出現如下錯誤。
分析
原因
由于Linux配置設定的用戶端連接配接端口用盡,無法建立socket連接配接所緻,雖然socket正常關閉,但是端口不是立即釋放,而是處于TIME_WAIT狀态,預設等待60s後才釋放。
由于用戶端頻繁的連伺服器,由于每次連接配接都在很短的時間内結束,導緻很多的TIME_WAIT,且用光了可用的端口号,是以新的連接配接沒辦法綁定端口,即"Cannot assign requested address"
是用戶端的問題不是伺服器端的問題。
通過netstat,可檢視處于TIME_WAIT狀态的連接配接。
sudo sysctl -a | grep tw
sudo netstat -antp | grep TIME_WAIT | wc -l
TCP狀态圖
為什麼會出現這種情況?先看下TCP狀态圖。
TCP的連接配接有11種狀态。
- ESTABLISHED
The socket has an established connection.
- SYN_SENT
The socket is actively attempting to establish a connection.
- SYN_RECV
A connection request has been received from the network.
- FIN_WAIT1
The socket is closed, and the connection is shutting down.
- FIN_WAIT2
Connection is closed, and the socket is waiting for a shutdown from the remote end.
- TIME_WAIT
The socket is waiting after close to handle packets still in the network.
- CLOSE
The socket is not being used.
- CLOSE_WAIT
The remote end has shut down, waiting for the socket to close.
- LAST_ACK
The remote end has shut down, and the socket is closed. Waiting for acknowledgement.
- LISTEN
The socket is listening for incoming connections.
- CLOSING
Both sockets are shut down but we still don't have all our data sent.
通過netstat指令可能還能看到UNKNOWN狀态。
- UNKNOWN
The state of the socket is unknown.
TCP規則規定如何基于目前狀态及在該狀态下所接收的分節從一個狀态轉換到另一個狀态。
比如,
- 當某個應用程序在CLOSED狀态下執行主動打開時,TCP将發送一個SYN,且新的狀态是SYN_SENT。
- 如果這個TCP接着接收到一個帶ACK的SYN,它将發送發送一個ACK,且新的狀态是ESTABLISHED。這個狀态是絕大多數資料傳送發生的狀态。
ESTABLISHED狀态轉移
- 如果某個應用程序在接收到一個FIN之前調用close(即主動關閉),那就轉換到FIN_WAIT_1狀态。
- 如果某個應用程序在ESTABLISHED狀态期接收到一個FIN(即被動關閉),那就轉到CLOSE_WAIT狀态。
TIME_WAIT狀态
TIME_WAIT狀态又稱為2MSL等待狀态。
所謂MSL (Maximum Segment Lifetime)指的是封包段在網絡記憶體活的最大時間。
RFC 793建議的MSL為2分鐘,具體的實作中一般為30秒或1分鐘。
TIME_WAIT狀态能夠
- 可靠的實作TCP全雙工連接配接的終止
當TCP執行一個主動關閉,并發回最後一個ACK,該連接配接必須在TIME_WAIT狀态停留2MSL時間。
這樣可讓TCP再次發送最後的ACK以防這個ACK丢失(如果丢失的話,另一端逾時并重發最後的FIN)。
- 允許老的重複分節在網絡中消逝
假設在192.168.1.254:1500和192.168.11.219:21端口之間有一個TCP連接配接。關閉這個連接配接,過一段時間後在相同的
IP:port之間建立另外一個連接配接。
後一個連接配接稱為前一個連接配接的化身(incarnation),因為連接配接的IP:port相同,TCP必須防止某個連接配接的老的分組在該
連接配接終止後再現,進而被誤解成屬于同一個連接配接的某個新的化身。
為做到這一點,TCP将不給處于TIME_WAIT狀态的連接配接發起新的化身。既然TIME_WAIT狀态的持續時間是2MSL,這就足以
讓某個方向上的分組最多存活MSL秒即被丢棄,另一個方向上的應答最多存活MSL秒被丢棄。
通過實施這一個規則,就能保證每成功建立一個TCP連接配接時,來自該連接配接先前化身的老的重複分組都已經在網絡中消逝了。
再分析
- 隻有主動關閉的一方才會進入TIME_WAIT狀态,被動關閉方不會。處在TIME-WAIT的狀态下,需要等2MSL時間後,系統才會回收這條連接配接,端口才可以繼續被使用。
- 一般來說,都是用戶端執行主動關閉,服務端都是被動關閉的。因為用戶端重新連接配接時,一般會使用一個新的端口号(通常都是系統配置設定的),是以不會受TIME_WAIT的限制。
- 為了防止TIME_WAIT對服務端的影響——服務端一般都使用的是固定的知名端口,如果重新開機的話(主動關閉),很可能因為TIME_WAIT的限制無法建立建立連接配接。是以socket API提供了SO_REUSEADDR選項,它讓調用者可以對處于2MSL狀态(即TIME_WAIT狀态)的本地端口進行指派。
方案
1. 修改系統TCP參數
修改運作ossutil 所在機器上的系統tcp參數。
- 調低端口釋放後的等待時間,預設為60s,修改為30s或者15s
sysctl -w net.ipv4.tcp_fin_timeout=30
- tcp_tw_resue, 預設為0,修改為1,釋放TIME_WAIT端口給新連接配接使用
sysctl -w net.ipv4.tcp_tw_reuse=1
- 支援TCP時間戳
sysctl -w net.ipv4.tcp_timestamps=1
- 快速回收socket資源,預設為0,修改為1
sysctl -w net.ipv4.tcp_tw_recycle=1
- 持久化
sysctl -p
2. 增加可用端口
$ sysctl -a |grep port_range
net.ipv4.ip_local_port_range = 52768 60999 ====>52768~60999端口可用
修改該參數
$ vi /etc/sysctl.conf
net.ipv4.ip_local_port_range = 22768 60999 ====>22768~60999端口可用
Reference
- 《UNIX網絡程式設計》
- 《TCP/IP詳解 卷1:協定》第18.6.1章節
- https://www.zhihu.com/question/40986079
- https://blog.csdn.net/wenqian1991/article/details/39667131