壓測場景下的 TIME_WAIT 處理

簡介: 壓測場景下的 TIME_WAIT 處理

 

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]。

  1. TW 是標準的一部分,不代表TCP端口或者連接狀態異常。(這個概念很重要,避免陷入某些不必要的陷阱。)
  2. 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]。



  1. 配置 max_tw_buckets,連接請求發起方接收方通用,但需要注意這個選項本身有違 TW 設計的初衷。

net.ipv4.tcp_max_tw_buckets = 5000

  1. 配置 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




  1. Windows 7 / Windows Server 2008 R2 及其之後的版本

端口範圍:
netsh int ipv4 set dynamicport tcp start=1025 num=64511
TW 超時時間:
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 的形成原因,相應的解決方案也就比較清楚了。

  1. 降低應用程序創建端口的速度。考慮連接持續時間和TW超時時間,計算相對合理的連接建立速度。不過,物理機操作系統、CPU/內存、網絡IO等均可能影響連接狀態,精確計算很難;同時,就應用程序而言,降低端口創建速度需要額外的邏輯,可能性不大。
  2. 在這個壓測場景下,通過增加機器的方式來變相減少端口的需求。壓測一般考慮某個固定閾值下整體系統的響應情況。在壓力固定的情況下,可以通過分散壓力的方式來減少端口資源的佔用。
  3. 改變連接的行爲,使用持久連接(注:非HTTP的長連接),例如針對500個併發,僅建立 500 個連接。好處顯而易見,但壞處也很明顯,持久連接不大符合用戶真實行爲,壓測結果可能失真。同時,該方法需要應用程序上的支持。
  4. 不讓機器的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配置選項說明

作者:陳鴿

原文鏈接

本文爲阿里雲原創內容,未經允許不得轉載

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