NGINX應用性能優化指南(第五部分):吞吐量
【編者的話】本文是“NGINX應用性能優化指南”系列文章的第五篇,主要介紹了如何從吞吐量方面實現NGINX應用性能優化。
注:本文最初發布于MaxCDN博客,InfoQ中文站在獲得作者授權的基礎上對文章進行了翻譯。
正文
NGINX反向代理配置設置了兩個網絡 路徑 :客戶端到代理和代理到服務器。這兩個路徑不僅 “HTTP跨度(HTTP spans)” 不同,TCP網絡傳輸域也不同。
尤其是提供大資源時,我們的目標是確保TCP充分利用了端到端連接。理論上講,如果TCP流同HTTP數據緊密打包在一起,而且數據盡可能快地發送出去,那么我們就會獲得最大的吞吐量。
但是,我時常在想,為什么我沒有看到更快的下載速度。知道這種感覺嗎?所以讓我們深入底層,了解下基本原理。
網絡傳輸入門
TCP采用兩個基本原則決定何時發送以及發送多少數據:
- 流量控制是為了確保接收者可以接收數據;
- 擁塞控制是為了管理網絡帶寬。
流量控制是通過 接收者 指定的接收窗口實現的,后者會規定接收者一次希望接收和存儲的最大數據量。這個窗口可能會變大——從幾個KB到若干MB——這取決于測得的連接 帶寬延遲乘積 (BDP,對此下文會有更多介紹)。
擁塞控制是由 發送者 實現為RWND大小的一個約束函數。發送者會將它傳輸的數據量限制為CWND和RWND的最小值。可以將此看作是遵從“網絡公平性”的一種自我約束。窗口大小(CWND)會隨著時間增長,或者隨著發送者接收到先前傳輸的數據的確認而增長。如果檢測到網絡擁塞,那么它也會縮小窗口。
同時,發送者和接收者在決定最大可用TCP吞吐量時各自扮演一個重要的角色。如果接收者的RWND太小,或者發送者對網絡擁塞過于敏感或者對網絡擁塞減退反應太慢,那么TCP吞吐量都不會是最理想的。
管道填充
網絡連接通常以管道為模型。發送者在一端泵入數據,接收者在另一端抽取數據。
BDP(以KB或MB為單位)是比特率同RTT的乘積,是一個表示需要多少數據填充管道的指標。例如,如果你在使用一個100Mbps的端到端連接,而RTT為80毫秒,那么BDP就為1MB(100 Mbps * 0.080 sec = 1 MB)。
TCP會設法填充管道,并保證沒有管道泄露或破裂,因此,BDP是RWND的理想值:TCP可以發出的最大動態(還沒有收到接收者的確認)數據量。
假設有足夠的數據待發送(大文件),而且沒有什么阻止發送應用程序(NGINX)以管道能夠接受的速度向管道泵入數據,RWND和CWND可能會成為實現最大吞吐量的限制因素。
大多數現代TCP棧會使用TCP時間戳以及窗口縮放選項自動優化這些參數。但是舊系統不會,有些應用程序會出現異常行為。因此,有兩個明顯的問題:
- 我如何檢查?
- 我如何修復?
我們下面會處理第一個問題,但是修復TCP涉及學習如何優化TCP棧——這本身就是一項全職工作。更可行的方案是TCP加速軟件或硬件。而且,這類供應商非常多,包括我每天都在研究的產品 SuperTCP 。
檢查RWND和CWND
設法確認RWND或CWND是否是限制因素,包括將它們同BDP進行比較。
為此,我們會嗅探在(無頭)NGINX代理上使用 tcpdump 工具進行大資源HTTP(S)傳輸的數據包,并將捕獲的文件加載到帶有圖形界面的機器上的 Wireshark 中。然后,我們可以繪制一個有意義的圖形,從而對這些基本變量是否得到了正確設置有一些了解。
# tcpdump -w http_get.pcap -n -s 100 -i eth0 port <80|443>
如果你使用了一個不同的捕獲過濾器,那么只要確保它捕獲了TCP HTTP對話的雙向數據。另外,還要確保是在 發送設備 上進行捕獲,因為我們需要使用Wireshark正確地計算動態數據量。(在接收者一端進行捕獲會使Wireshark相信RTT接近為0,因為接收者ACK可能會在數據進來后立即發送出去)。
將 http_get.pcap 文件加載到Wireshark中,找到感興趣的HTTP流,然后仔細看下它的 tcp.stream 索引:
打開Statistics->IO Graph,并進行如下配置:
- Y-axis -> Unit: Advanced
- Scale: Auto
- Graph 5 (pink)
- Filter: tcp.dstport == <80|443> && tcp.stream == < index>
- Calc: MAX and tcp.window_size
- Style: Impulse
- Graph 4 (blue)
- Filter: tcp.srcport == <80|443> && tcp.stream == < index>
- Calc: MAX and tcp.analysis.bytes_in_flight
- Style: FBar
接下來,務必按下(啟用)Graph 4和Graph 5按鈕,根據那些結果進行繪圖。下面的例子可能是你期望看到的:
我使用一個100Mbps的連接從一個80毫秒遠的NGINX代理上GET一個128MB的文件(從AWS俄勒岡州到我們在加拿大渥太華的辦公室)。相應的BDP為1MB。
注意看下RWND(粉色)的變化,開始時很小,數個往返后增長到稍稍超過1MB。這證明 接收者 能夠調整RWND,并且可以察覺BDP(好極了)。或者,如果我們看到RWND變小了(又稱為關閉中),那表明接收應用程序讀取數據的速度不夠快——也許沒有獲得足夠的CPU時間。
對于發送者的性能——CWND(藍色)——我們想要一個指征,就是動態數據量會受到RWND限制。我們看到,在 3秒到6秒 這個時間段里,NGINX代理能夠發出的動態數據量是RWND允許的最大數據量。這證明發送者能夠推送足夠的數據以滿足BDP。
不過,在快到 6秒 時,似乎有東西出現了 大問題 。發送者發出的動態數據量顯著減少。自我約束行為通常是由發送者檢測到擁塞引起的。回想一下,GET響應從西海岸傳送到東海岸,遇到網絡擁塞還是很可能的。
識別網絡擁塞
當發送者檢測到擁塞,它會縮小CWND,以便減少它對網絡擁塞的貢獻。但是,我們如何才能知道?
通常,TCP棧可以使用兩類指示器檢測或度量網絡擁塞:數據包丟失和延遲變化(bufferbloat)。
- 數據包丟失 :任何網絡都會出現數據包丟失,Wi-Fi網絡尤為突出,或者在網絡元素積極管理它們的隊列(比如隨機早期丟棄)的時候,或者在它們根本沒有管理它們的隊列(尾部丟棄)的時候。TCP會將丟失ACK或者從接收者那里收到了非遞增ACK(又稱重復ACK)當作數據包丟失。
-
Bufferbloat是由數據包積壓越來越多導致的端點間延遲(RTT)增加。
TIP:使用簡單的工具 ping 或者更加復雜的工具 mtr ,有時候可以檢測到有意義的bufferbloat——尤其是當發送者推送數據速率較高的時候。那會給你深刻的印象,讓你對更深層次的網絡上可能正在發生什么有個真實的感受。
在同一個Wireshark IO Graph窗口中加入下列內容:
- Graph 2 (red)
- Filter: tcp.dstport == <80|443> && tcp.stream == < index>
- Calc: COUNT FIELDS and tcp.analysis.duplicate_ack
- Style: FBar
- 另外將Scale改為 Logarithmic
瞧!證據有了,接收者發送了重復ACK,表明實際上有數據包丟失。這解釋了為什么發送者縮小了CWND,即動態數據量。
你可能還想尋找 tcp.analysis.retransmission 出現的證據,當出現數據包丟失報告,數據包必須重發時,或者當發送者等待來自接收者的ACK超時時(假設數據包丟失或者ACK丟失)。在后一種情況下,查下 tcp.analysis.rto 。
對于上述兩種情況,務必將Filter設置為 tcp.srcport= * <80|443> * ,因為重傳源于發送者。
來自: infoq