https://cloud.tencent.com/developer/article/1004356
案例一:同事隨手寫個壓力測試程序,其實現邏輯為:每秒鍾先連續發N個132字節的包,然后連續收N個由后台服務回顯回來的132字節包。其代碼簡化如下:
char sndBuf[132]; char rcvBuf[132]; while (1) { for (int i = 0; i < N; i++){ send(fd, sndBuf, sizeof(sndBuf), 0); ... } for (int i = 0; i < N; i++) { recv(fd, rcvBuf, sizeof(rcvBuf), 0); ... } sleep(1); }
在實際測試中發現,當N大於等於3的情況,第2秒之后,每次第三個recv調用,總會阻塞40毫秒左右,但在分析Server端日志時,發現所有請求在Server端處理時耗均在2ms以下。
當時的具體定位過程如下:先試圖用strace跟蹤客戶端進程,但奇怪的是:一旦strace attach
上進程,所有收發又都正常,不會有阻塞現象,一旦退出strace,問題重現。
經同事提醒,很可能是strace改變了程序或系統的某些東西(這個問題現在也還沒搞清楚),
於是再用tcpdump抓包分析,發現Server后端在回現應答包后,Client端並沒有立即對該數據進行ACK確認,而是等待了近40毫秒后才確認。
經過Google,並查閱《TCP/IP詳解卷一:協議》得知,此即TCP的延遲確認(Delayed Ack)機制。
其解決辦法如下:在recv系統調用后,調用一次setsockopt
函數,設置TCP_QUICKACK
。最終代碼如下:
char sndBuf[132]; char rcvBuf[132]; while (1) { for (int i = 0; i < N; i++) { send(fd, sndBuf, 132, 0); ... } for (int i = 0; i < N; i++) { recv(fd, rcvBuf, 132, 0); setsockopt(fd, IPPROTO_TCP, TCP_QUICKACK, (int[]){1}, sizeof(int)); } sleep(1); }
案例二:在營銷平台內存化CDKEY版本做性能測試時,發現請求時耗分布異常:90%的請求均在2ms以內,而10%左右時耗始終在38-42ms之間,這是一個很有規律的數字:40ms。
因為之前經歷過案例一,所以猜測同樣是因為延遲確認機制引起的時耗問題,經過簡單的抓包驗證后,通過設置TCP_QUICKACK
選項,得以解決時延問題。
延遲確認機制
在《TCP/IP詳解卷一:協議》第19章對其進行原理進行了詳細描述:TCP在處理交互數據流(即Interactive Data Flow
,區別於Bulk Data Flow
,即成塊數據流,典型的交互數據流如telnet、rlogin等)時,采用了Delayed Ack機制以及Nagle算法【累積小數據,然后一起發】來減少小分組數目。
書上已經對這兩種機制的原理講的很清晰,這里不再做復述。本文后續部分將通過分析TCP/IP在Linux下的實現,來解釋一下TCP的延遲確認機制。
1.為什么TCP延遲確認會導致延遲?
其實僅有延遲確認機制,是不會導致請求延遲的(初以為是必須等到ACK包發出去,recv系統調用才會返回)。
一般來說,只有當該機制與Nagle算法或擁塞控制(慢啟動或擁塞避免)混合作用時,才可能會導致時耗增長。
我們下面來詳細看看是如何相互作用的:
延遲確認與Nagle算法
我們先看看Nagle算法的規則(可參考tcp_output.c文件里tcp_nagle_check函數注釋):
1)如果包長度達到MSS,則允許發送;
2)如果該包含有FIN,則允許發送;
3)設置了TCP_NODELAY選項,則允許發送;
4)未設置TCP_CORK選項時【打開nagle算法】,若所有發出去的包均被確認,或所有發出去的小數據包(包長度小於MSS)均被確認,則允許發送。
對於規則4),就是說要求一個TCP連接上最多只能有一個未被確認的小數據包,在該分組的確認到達之前,不能發送其他的小數據包。
如果某個小分組的確認被延遲了(案例中的40ms),那么后續小分組的發送就會相應的延遲。也就是說延遲確認影響的並不是被延遲確認的那個數據包,而是后續的應答包。
1 00:44:37.878027 IP 171.24.38.136.44792 > 175.24.11.18.9877: S 3512052379:3512052379(0) win 5840 <mss 1448,wscale 7> 2 00:44:37.878045 IP 175.24.11.18.9877 > 171.24.38.136.44792: S 3581620571:3581620571(0) ack 3512052380 win 5792 <mss 1460,wscale 2> 3 00:44:37.879080 IP 171.24.38.136.44792 > 175.24.11.18.9877: . ack 1 win 46 ...... 4 00:44:38.885325 IP 171.24.38.136.44792 > 175.24.11.18.9877: P 1321:1453(132) ack 1321 win 86 5 00:44:38.886037 IP 175.24.11.18.9877 > 171.24.38.136.44792: P 1321:1453(132) ack 1453 win 2310 6 00:44:38.887174 IP 171.24.38.136.44792 > 175.24.11.18.9877: P 1453:2641(1188) ack 1453 win 102 7 00:44:38.887888 IP 175.24.11.18.9877 > 171.24.38.136.44792: P 1453:2476(1023) ack 2641 win 2904 8 00:44:38.925270 IP 171.24.38.136.44792 > 175.24.11.18.9877: . ack 2476 win 118 9 00:44:38.925276 IP 175.24.11.18.9877 > 171.24.38.136.44792: P 2476:2641(165) ack 2641 win 2904 10 00:44:38.926328 IP 171.24.38.136.44792 > 175.24