TCP協議Nagle算法和Delayed ACK相互影響實例分析

建議:閱讀本文之前,最好對於TCP的發送、重發以及ACK機制有所瞭解。

問題描述

最近在一個消息中間件系統(該消息中間件由客戶端SDK和服務端Server組成)的性能測試時,發現每個請求的響應時間大概在40ms-50ms之間,這明顯過大了。最終定位,是因爲SDK沒有禁用TCP的Nagle算法導致的。但其根本原理是因爲TCP的Delayed Ack機制和Nagle Algorithm相互影響導致的。

概念詳解

TCP-Delayed Ack
目的:用於防止只發送一個單獨的Ack包,而是希望發送的包是一個Ack+一份數據組成一個包,這樣能較少交互次數,減少網絡資源消耗。這個設定是基於對於一般交互場景的一個基本假設:數據接收方會生成一個響應給數據發送方。
原理:當數據接收方收到一個TCP包之後,先不迴應ACK包,而是等待一定的時間(本例中是40ms),直到
1. 數據接收方發送一份響應數據
當數據接收方,收到足夠的數據,進行業務處理後,一般會返回業務響應,這時會立即返回:業務響應+ACK。
2. 接收到連續的TCP包
當服務接收端接收到1號TCP包後,會先延遲發送1號包的ACK,等待2號包到來,2號包到達時,則立即返回一次ACK;當3號包到來時,會延遲3號包的ACK,等待4號包到來,4號包到達時,則立即返回一次ACK……
那麼,這樣就造成偶數序號的TCP包到達的時候,就會立即返回一次ACK;而奇數序號的包到達時,則延遲ACK響應,等待後續的偶數包到來。
3. 超時
超過40ms。

TCP-Nagle Algorithm
目的:用於防止發送大量的小包,降低網絡資源消耗。
原理:當數據發送方寫入TCP緩衝區的數據小於MSS(最大報文長度),則暫不發送,等待寫入的數據達到MSS再發送;除非在等待的過程中,發送端發送出去的所有TCP報文均已被ACK,這樣就可以不用等待寫入數據達到MSS,直接發送出去了。我們稱沒有達到MSS的報文爲小包的話,那麼其實TCP-Nagle Algorithm就保證了一個連接在一個時刻,有且只能有一個沒有被確認的小包。
關於以上兩點,可以參考這篇文章,介紹的很詳細:http://www.stuartcheshire.org/papers/NagleDelayedAck/

過程分析

該消息中間件系統在做性能測試時,SDK沒有禁用Nagle算法,而Server端禁用了;測試時的消息長度爲消息頭(12)+消息體(14)Byte,TCP MSS是1460Byte;服務端和SDK交互採用類似TCP三次握手的確認機制來保證高可靠性。那麼考慮如下過程:
1. SDK端發送第一條消息,寫入了TCP Buffer,雖然未達到MSS,但是因爲沒有需要確認的包,所以會立即發送;
2. Server端收到TCP包後,就延遲ACK響應;同時SDK端由於啓用了Nagle算法,並且存在沒有ACK的包,因此處於等待中;
3. Server端業務層解包進行業務處理,處理完成後,立即發送業務響應,並捎帶ACK返回給SDK;
4. SDK端收到了Server端返回的響應和ACK後,立即回覆業務確認消息,並捎帶ACK;
5. 由於Server端收到業務確認消息後,不用再返回響應給SDK,因此延遲ACK確認;
6. SDK端發送第二條消息,但是因爲啓用了Nagle算法,所以必須等待ACK。直到第5步的延遲確認到達,則立即發送第二條消息。

Tcpdump抓包如下:

2015-05-25 18:42:49.736897 IP 100.84.52.90.58237 > 100.84.73.45.9090: P 71:101(30) ack 20 win 16420
E..FU3@.=..OdT4ZdTI-.}#...(.R.mpP.@$.).......)....
queueAThis is test 
2015-05-25 18:42:49.737763 IP 100.84.73.45.9090 > 100.84.52.90.58237: P 20:39(19) ack 101 win 46
E..;Fn@[email protected]-dT4Z#..}R.mp..) P...F].......*......SUCCESS
2015-05-25 18:42:49.744738 IP 100.84.52.90.58237 > 100.84.73.45.9090: P 101:113(12) ack 39 win 16415
E..4U4@.=..`dT4ZdTI-.}#...) [email protected]......
2015-05-25 18:42:49.784810 IP 100.84.73.45.9090 > 100.84.52.90.58237: . ack 113 win 46
E..(Fo@.@..1dTI-dT4Z#..}R.m...).P.......
2015-05-25 18:42:49.785438 IP 100.84.52.90.58237 > 100.84.73.45.9090: P 113:143(30) ack 39 win 16415
E..FU5@.=..MdT4ZdTI-.}#...).R.m.P.@..........)....
queueAThis is test 
2015-05-25 18:42:49.785450 IP 100.84.73.45.9090 > 100.84.52.90.58237: . ack 143 win 46
E..(Fp@.@..0dTI-dT4Z#..}R.m...)3P.......
2015-05-25 18:42:49.786147 IP 100.84.73.45.9090 > 100.84.52.90.58237: P 39:58(19) ack 143 win 46
E..;Fq@[email protected]-dT4Z#..}R.m...)3P...F].......*......SUCCESS
2015-05-25 18:42:49.790470 IP 100.84.52.90.58237 > 100.84.73.45.9090: P 143:173(30) ack 58 win 16410
E..(Fo@.@..1dTI-dT4Z#..}R.m...).P.......

總結:通過以上過程可以發現,由於第5步的延遲ACK,導致SDK發送消息延遲,最終導致了性能下降。如果SDK端關閉的Nagle算法,SDK就可以立即發送第二條消息,而不會受到Nagle算法的約束,一直等待第5步的ACK到達。

代碼說明

在Netty中,可以通過設置tcpNoDelay選項,開啓或者禁用Nagle算法。

// 用來禁用TCP的Nagle算法。
bootstrap.setOption("tcpNoDelay", true);

// Netty底層其實就是使用JDK中的Socket.class的setTcpNoDelay方法來設置。
/**
* Enable/disable TCP_NODELAY (disable/enable Nagle's algorithm).
*/
public void setTcpNoDelay(boolean on) throws SocketException;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章