
1. 序
某專有雲項目具備壓測場景,在Windows的壓測機上用 LoadRunner 進行業務的壓力測試,壓測運作一段時間後出現大量端口無法配置設定的報錯。
其實通過問題描述,以及 Windows的報錯資訊基本确定是壓測機的問題。但可能原因較多,一直未能達成一緻。是以,趁機分析了用戶端的壓測機成為壓測瓶頸的可能,除了CPU、網絡、I/O 等機器性能參數外,仍需考慮網絡協定引入的資源短缺問題。
注:以下内容的目的是理清TCP協定中比較模糊的内容,對協定比較熟悉的可以忽略。
2. TIME_WAIT基礎:RFC 793 TCP協定
衆所周知, TCP存在三次握手,四次揮手過程。其具體設計的目的,簡而言之,是為了在不穩定的實體網絡環境中確定可靠的資料傳輸;是以,TCP在具體實作中加入了很多異常狀況的處理,整體協定就變得比較複雜。
要了解TCP協定,推薦閱讀 RFC 793,可參考文後資料[1]了解詳情。同時,也要了解“TCP state transition”狀态機,如下圖所示,可參考文後資料[2]了解詳情。
圖1. TCP狀态轉換圖
本文僅針對 TW 在TCP協定中的作用進行讨論,不涉及整體協定的分析。四次揮手後的TIME_WAIT 狀态,後續将以TW縮寫替代。
2.1 TW 作用
-
首先,主要作用是保證TCP連接配接關閉的可靠性。
考慮下在四次揮手過程中,如果主動關閉方發送的LAST_ACK丢失,那麼被動關閉方會重傳FIN。此時,如果主動關閉方對應的TCP Endpoint沒有進入TW狀态而是直接在核心中清理了,根據協定,主動關閉方會認為自己沒有打開過這個端口,而以RST響應被動關閉方重傳的FIN。最終該行為導緻被動關閉方認為連接配接異常關閉,在業務上可能會收到異常報錯等情況。
-
其次,TW狀态同時也能避免相同的TCP端口收到在網絡上前一個連接配接的重複資料包。
理論上,資料包在網絡上過期時間對應即MSL(Maximal Segment Lifetime),随着作業系統的不斷發展,也有例外情況,這部分搜尋PAWS應該可以看到不少類似的文章說明。
- 再次,端口進入 TW 狀态 同時也避免了被作業系統快速重複使用的可能。
2.2 TW形成的原因
當一台主機作業系統主動關閉TCP Endpoint(socket)時,該TCP Endpoint進入TW狀态。以Windows為例,Windows核心會對 TCP Endpoint 資料結構進行相應清理,然後放入額外的 TW queue 中,設定2MSL 的定時器,等待定時器逾時後調用對應的釋放代碼。Linux上的實作也是類似。
目前較多的說法是"TCP連接配接"進入TW ,但我們可能需要了解 "連接配接" 其實是抽象的概念。實際"連接配接"在邏輯上存在,因為用戶端和伺服器端及中間可能涉及的4層裝置同時為一次傳輸建立了關聯的TCP資源(Endpoint,或者 Session)。準确了解TW狀态,即TCP EndpointTW進入TW狀态。
2.3 小結
TW 是為了保證 TCP 連接配接正常終止(避免端口被快速複用),也是為了保證網絡中迷失的資料包正常過期(防止前一個連接配接的資料包被錯誤的接收)。
TW暗殺術,可參考文後資料[3]了解詳情。
3. 概念澄清
歡迎讨論
幾個可能比較模糊的地方,明确如下:
1.作為連接配接雙方,用戶端和伺服器端的TCP Endpoint都可能進入 TW 狀态(極端情況下,可能雙方同時進入 TW 狀态)。
該情況在邏輯上是成立的,可參考文後資料[4]了解詳情。
2.TW 是标準的一部分,不代表TCP端口或者連接配接狀态異常。(這個概念很重要,避免陷入某些不必要的陷阱。)
3.CLOSE_WAIT 盡管也是标準的一部分,但它的出現預示着本端的 TCP Endpoint 處于半關閉狀态,原因常常是應用程式沒有調用 socket 相關的 close 或者 shutdown。可能的原因是應用程式仍有未發送完成的資料,該情況下CLOSE_WAIT 最終還是會消失的。 具體描述這部分,長期有 CLOSE_WAIT 狀态的端口緩慢累積,這種情況是需要引起注意的,累積到一定程度,端口資源就不夠了。
針對前面的 TCP Endpoint 這個詞語,可能很多人不太了解,這邊也簡單說明下:
在Windows 2008 R2之前,socket是使用者态(user mode) 的概念,大多數Windows socket應用程式基本都基于Winsock開發,由中間層AFD.sys 驅動翻譯成核心 tcpip.sys 協定棧驅動 所能接受的TCP Endpoint資料結構。在2008 R2之後,微軟為了友善核心的網絡程式設計,在Windows Kernel中提供WSK,即Winsock在核心的實作。文中提到的TCP Endpoint是在Windows核心中由TCPIP.sys驅動檔案實作的TCP資料結構,也對應Linux上的socket。該文簡單以 Endpoint 代指核心的"socket"。
4. TW 優化手段
對于Linux,優化手段已經進行了很多讨論了,以Centos為例。
1.在timestamps啟用的情況下,配置 tcp_tw_reuse 和tcp_tw_recycle。
針對用戶端,連接配接請求發起方。
net.ipv4.tcp_timestamps = 1
net.ipv4.tcp_tw_reuse = 1
針對伺服器端,連接配接請求接收方
net.ipv4.tcp_timestamps = 1
net.ipv4.tcp_tw_recycle = 1
注:tcp_tw_recycle的啟用會帶來一些 side effect,具體在NAT位址轉換場景下,容易發生連接配接異常問題。
可參考文後資料[4]了解詳情。
2.配置 max_tw_buckets,連接配接請求發起方接收方通用,但需要注意這個選項本身有違 TW 設計的初衷。
net.ipv4.tcp_max_tw_buckets = 5000
3.配置 ip_local_port_range,連接配接請求發起方。
net.ipv4.ip_local_port_range = 5000 65535
針對Windows ,資料較少,這邊借之前的工作經驗,總結如下:
1.Windows Vista / Windows Server 2008 之前的作業系統,系統資料庫
端口範圍:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters
MaxUserPort = 0n65534
TW 逾時時間:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters
TcpTimedWaitDelay = 0n30
2.Windows 7 / Windows Server 2008 R2 及其之後的版本
netsh int ipv4 set dynamicport tcp start=1025 num=64511
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters
TcpTimedWaitDelay = 0n30
Windows Server 2012 and earlier: 30-300 (decimal)
Windows 8 and earlier: 30-300 (decimal)
Windows Server 2012 R2 and later: 2-300 (decimal)
Windows 8.1 and later: 2-300 (decimal)
注:
- 任何涉及系統資料庫的修改,隻有重新開機機器才會生效。
- 與 Linux不同,Windows 沒有快速回收機制,不存在快速回收 TW 的可能,隻能等待2MSL過期(即TcpTimedWaitDelay)。
-
Windows唯一能快速回收TW狀态的Endpoint 的情況:
新連接配接請求的SEQ序列号>TW狀态的Endpoint記錄的SEQ序列号。
此時,核心會認為該 SYN 請求合法。 這裡,這個TW 狀态的 TCP Endpoint 一定是在服務端(通過socket accept 打開的 服務端口)。(為了這個能力,Windows 的 RFC 1323 選項必須打開,内容可以自行搜尋。)
5. 壓測用戶端無法配置設定端口的原因分析
端口無法配置設定有兩種可能:
- 完全随機的動态端口請求,報錯端口配置設定異常,基本是作業系統沒有可用端口。
- 指定端口的綁定申請報錯端口配置設定異常,可能存在端口使用沖突問題。
針對第一種情況,首先需要通過 netstat -ano 進行快速檢查,分析是否存在端口占滿的情況,以及占滿端口的TCP Endpoint狀态。針對不同的狀态,考慮不同的方案。
比如,極端情況下,沒有任何異常的伺服器上,端口配置設定失敗問題,可參考文後資料[5]了解詳情。
以Windows作業系統TW狀态Endpoint占滿可用端口場景為例(在Linux上發生的可能性較低),分析問題前需要大概了解 Windows 上端口配置設定原理。
- Windows和Linux在動态配置設定端口的機制上有很大的不同。
- Linux以粗淺的了解應該是針對五元組的配置設定,即可能存在相同的動态端口通路不同伺服器的服務端口。
- Windows的動态端口配置設定實作基于Bitmap查找,無論通路哪裡,動态端口的池子最大為 1025 – 65536,即64511個。
- 考慮到最短30秒的 TW 逾時時間,如果按照 64511/29 = 2225 ports/s 的速度去建立端口,那麼很可能在30秒後持續發生端口無法配置設定的問題。
- 這還是在連接配接處理比較快速的情況下,如果連接配接建立後不關閉,或者關閉時間比較久,建立端口的速度仍需持續下降來規避端口問題。
了解了 TW 的形成原因,相應的解決方案也就比較清楚了。
- 降低應用程式建立端口的速度。考慮連接配接持續時間和TW逾時時間,計算相對合理的連接配接建立速度。不過,實體機作業系統、CPU/記憶體、網絡IO等均可能影響連接配接狀态,精确計算很難;同時,就應用程式而言,降低端口建立速度需要額外的邏輯,可能性不大。
- 在這個壓測場景下,通過增加機器的方式來變相減少端口的需求。壓測一般考慮某個固定門檻值下整體系統的響應情況。在壓力固定的情況下,可以通過分散壓力的方式來減少端口資源的占用。
- 改變連接配接的行為,使用持久連接配接(注:非HTTP的長連接配接),例如針對500個并發,僅建立 500 個連接配接。好處顯而易見,但壞處也很明顯,持久連接配接不大符合使用者真實行為,壓測結果可能失真。同時,該方法需要應用程式上的支援。
-
不讓機器的TCP Endpoint進入TW狀态,可參考以下2種方案。
a) 不讓該機器主動關閉連接配接,而讓對方主動關閉。這樣,該主機進入被動關閉程序,在應用關閉TCP Endpoint之後,可直接釋放端口資源。
一些協定本身就有控制是否保持連接配接或者請求對方關閉連接配接的行為或者參數,在考慮這類問題的時候,可以适當進行利用。比如 HTTP 的長短連接配接,可參考文後資料[4]了解詳情。
b) 通過TCP Reset強制釋放端口。TCP Reset可以由任何一方發出,無論是發送方還是接收方,在看到TCP Reset之後會立刻将對應TCP Endpoint拆除。
這裡,可設定 socket 的 SO_LINGER選項,比如配置Nginx,可參考文後官方文檔[6]了解詳情。
圖2:Nginx Lingering配置參考說明
針對壓測工具本身,官方網站上也有類似 ABRUPT 選項,可參考文後官方文檔[7]了解詳情。
圖3:LoadRunner ABRUPT配置選項說明
參考文檔
[1] RFC 793:
https://tools.ietf.org/html/rfc793[2] IBM TCP state transition:
https://www.ibm.com/support/knowledgecenter/en/SSLTBW_2.1.0/com.ibm.zos.v2r1.halu101/constatus.htm[3] TIME-WAIT Assassination Hazards in TCP:
https://tools.ietf.org/html/rfc1337[4] Tengine健康檢查引發大量TIME_WAIT堆積:
https://developer.aliyun.com/article/781244[5] CloudMonitor 引發的網絡問題排查一則:
https://developer.aliyun.com/article/682535[6] 配置Nginx:
http://nginx.org/en/docs/http/ngx_http_core_module.html#lingering_close[7] ABRUPT選項:
https://admhelp.microfocus.com/lr/en/2020_SP2-SP3/help/function_reference/Content/FuncRef/web/lrFR_web_set_sockets_option.htm?Highlight=web_set_socket_option#Shutdown我們是阿裡雲智能全球技術服務-SRE團隊,我們緻力成為一個以技術為基礎、面向服務、保障業務系統高可用的工程師團隊;提供專業、體系化的SRE服務,幫助廣大客戶更好地使用雲、基于雲建構更加穩定可靠的業務系統,提升業務穩定性。我們期望能夠分享更多幫助企業客戶上雲、用好雲,讓客戶雲上業務運作更加穩定可靠的技術,您可用釘釘掃描下方二維碼,加入阿裡雲SRE技術學院釘釘圈子,和更多雲上人交流關于雲平台的那些事。