概述:本文討論主機在發送一個TCP數據包後,如果遲遲沒有收到ACK,主機多久後會重傳這個數據包。主機從發出數據包到第一次TCP重傳開始,RFC中這段時間間隔稱爲retransmission timeout,縮寫做RTO。本文會先看看RFC中如何定義RTO,然後看看Linux中如何實現。本文旨在分享:當遇到了TCP層問題改如何去查找、閱讀文檔,該如何去在Linux源碼中尋求答案。
在分析MySQL Semi-sync故障時,我們用Tcpdump+Wireshark(感謝淘寶雕樑)抓住當時的網絡包傳送細節,觀察到了一次TCP重傳最終導致了Semi-sync超時:
看到兩次傳送間隔約201毫秒,即第一次傳輸201毫秒後,還沒有收到ACK響應,TCP認爲傳輸超時,開始重傳。
疑問:host和host之間的RTT大約是0.5毫秒,爲什麼第一次重傳需要等200毫秒?(我希望是<20ms)socket程序可以配置嗎RTO嗎?TCP有參數可配置RTO嗎?
翻開TCP/IP詳解找到關於TCP Retransmission章節,較詳細的介紹TCP的超時機制,書中是個概述,於是又找到RFC1122。
RFC1122的4.2.2.15和4.2.3.1都介紹了Retransmission Timeout的處理(說來慚愧,這是第一次閱讀TCP相關RFC)。
在RFC中搜索Retransmission發現RFC 793 1122 2988 6298都有對重傳算法、和初次重傳超時的描述。於是開始閱讀這個四個RFC,耗時約2小時,瞭解了大致的重傳超時算法。
概述:先根據該socket的RTT計算出SRTT(Smoothed Round Trip Time),然後根據一個最大、最小超時時間確定當前RTO。說明:srtt可以理解爲“平滑化”的RTT,即在保持計算簡單的情況儘量考慮歷史RTT。
詳細計算:SRTT = ( ALPHA * SRTT ) + ((1-ALPHA) * RTT)
基於SRTT,我們再來計算RTO:RTO = min[UBOUND,max[LBOUND,(BETA*SRTT)]]
UBOUND是RTO上線,ALPHA是平滑因子(smoothing factor, e.g., .8 to .9),BETA是一個延遲方差因子(BETA is a delay variance factor (e.g., 1.3 to 2.0))。
仔細看這兩個公式大概就能理解了RTO的計算了。
這裏對上面兩個公式做一個簡單的註釋:公式1中計算SRTT,ALPHA越接近於0,則表示SRTT越相信這一次的RTT;越接近於1,則表示SRTT越相信上次統計的RTT。公式二給RTO分別設置了一個上限和下限。
上面我們介紹的是初次重傳時的RTO,如果重傳後還沒收到另一端的響應,下一次重傳RTO則會指數增加,例如第一次重傳RTO是1,之後分別2,4,8,16...。
在RFC-2988和RFC-6298中又重新改進了RTO的計算方法,Linux中的實現即使參考RFC-2988。算法核心公式:
這裏說的是RHEL5.4的2.6.18內核,RFC-2988實現參考net/ipv4/tcp_input.c中的tcp_rtt_estimator和tcp_set_rto。可以看到,在Linux中alpha=1/8,RTO最小爲TCP_RTO_MIN。因爲我們的系統中RTT總是很小,所以RTO取值總是能夠取到TCP_RTO_MIN。
在看看TCP_RTO_MIN在Linux中的定義:
(這裏簡單的介紹介紹一下HZ,HZ可以理解爲1s,所以120*HZ就是120秒,HZ/5就是200ms。詳細的:HZ表示CPU一秒種發出多少次時間中斷--IRQ-0,Linux中通常用HZ來做時間片的計算,參考)
/proc/sys/net/ipv4/tcp_retries1 (integer; default: 3)
TCP嘗試了3次(tcp_retries1默認3)重傳後,還沒有收到ACK的話,則後續每次重傳都需要network layer先更新路由。
/proc/sys/net/ipv4/tcp_retries2 (integer; default: 15)
TCP默認最多做15次重傳。根據RTO(retransmission timeout)不同,最後一次重傳間隔大概是13到30分鐘左右。如果15次重傳都做完了,TCP/IP就會告訴應用層說:“搞不定了,包怎麼都傳不過去!”
回答前面的問題:即使RTT很小(0.8ms),但是因爲RTO有下限,最小必須是200ms,所以這是RTT再小也白搭;RTO最小值是內核編譯是決定的,socket程序中無法修改,Linux TCP也沒有任何參數可以改變這個值。