天天看點

RUDP傳輸那些事兒

RUDP的價值在于根據不同的傳輸場景進行不同的技術選型,可能選擇寬松的擁塞方式也可能選擇特定的重傳模式,但都是基于Expense(成本)、Latency(時延)、Quality(品質)三者之間來權衡,通過結合場景和權衡三角平衡關系RUDP或許能幫助開發者找到一個比較好的方案。

最近和很多實時音視訊領域的朋友交流中都有談論到RUDP(Reliable UDP),這其實是個老生常談的問題,RUDP在很多著名的項目上都有使用,例如google的QUIC和webRTC。在UDP之上做一層可靠,很多朋友認為這是很不靠譜的事情,也有朋友認為這是一個大殺器,可以解決實時領域裡大部分問題。作為在教育公司來說,學霸君在很多實時場景下确實使用RUDP技術來解決我們的問題,不同場景我們采用的RUDP方式也不一樣。先來看看學霸君哪些場景使用了RUDP:

1) 全局250毫秒延遲的實時1V1答疑,采用的是RUDP + 多點relay智能路由方案。

2) 500毫秒1080P視訊連麥互動系統,采用的是RUDP + PROXY排程傳輸方案。

3) 6方實時同步書寫系統,采用的是RUDP+redo log的可靠傳輸技術。

4) 弱網WIFI下Pad的720P同屏傳輸系統,采用的是RUDP +GCC實時流控技術。

5) 大型直播的P2P分發系統,通過RUDP + 多點并聯relay技術節省了75%以上的分發帶寬。

涉及到實時傳輸我們都會先考慮RUDP,RUDP應用在學霸君核心傳輸體系的各個方面,但不同的系統場景我們設計了不同的RUDP方式,是以基于那些激烈的讨論和我們使用的經驗我扒一扒RUDP。其實在實時通信領域存在一個三角平衡關系:成本,品質,時延三者的制約關系(圖1)

RUDP傳輸那些事兒

圖1

也就是說投入的成本、獲得的品質和通信的時延之間是一個三角制約(LEQ)關系,是以實時通信系統的設計者會在這三個制約條件下找到一個平衡點,TCP屬于是通過增大延遲和傳輸成本來保證品質的通信方式,UDP是通過犧牲品質來保證時延和成本的通信方式,是以在一些特定場景下RUDP更容易找到這樣的平衡點。RUDP是怎麼去找這個平衡點的,就要先從RUDP的可靠概念和使用場景來分析。

可靠的概念

在實時通信過程中,不同的需求場景對可靠的需求是不一樣的,我們在這裡總體歸納為三類定義:

盡力可靠:通信的接收方要求發送方的資料盡量完整到達,但業務本身的資料是可以允許缺失的。例如:音視訊資料、幂等性狀态資料。

無序可靠:通信的接收方要求發送方的資料必須完整到達,但可以不管到達先後順序。例如:檔案傳輸、白闆書寫、圖形實時繪制資料、日志型追加資料等。

有序可靠:通信接收方要求發送方的資料必須按順序完整到達。

RUDP是根據這三類需求和圖1的三角制約關系來确定自己的通信模型和機制的,也就是找通信的平衡點。

UDP為什麼要可靠

說到這裡可能很多人會說:幹嘛那麼麻煩,直接用TCP好了!确實很多人也都是這樣做的,TCP是個基于公平性的可靠通信協定,在一些苛刻的網絡條件下TCP要麼不能提供正常的通信品質保證,要麼成本過高。為什麼要在UDP之上做可靠保證,究其原因就是在保證通信的時延和品質的條件下盡量降低成本,RUDP主要解決以下相關問題:

端對端連通性問題:一般終端直接和終端通信都會涉及到NAT穿越,TCP在NAT穿越實作非常困難,相對來說UDP穿越NAT卻簡單很多,如果是端到端的可靠通信一般用RUDP方式來解決,場景有:端到端的檔案傳輸、音視訊傳輸、互動指令傳輸等等。

弱網環境傳輸問題:在一些WIFI或者3G/4G移動網下,需要做低延遲可靠通信,如果用TCP通信延遲可能會非常大,這會影響使用者體驗。例如:實時的操作類網遊通信、語音對話、多方白闆書寫等,這些場景可以采用特殊的RUDP方式來解決這類問題。

帶寬競争問題:有時候用戶端資料上傳需要突破本身TCP公平性的限制來達到高速低延時和穩定,也就是說要用特殊的流控算法來壓榨用戶端上傳帶寬,例如:直播音視訊推流,這類場景用RUDP來實作不僅能壓榨帶寬,也能更好的增加通信的穩定性,避免類似TCP的頻繁斷開重連。

傳輸路徑優化問題:在一些對延時要求很高的場景下,會用應用層relay的方式來做傳輸路由優化,也就是動态智能選路,這時雙方采用RUDP方式來傳輸,中間的延遲進行relay選路優化延時。還有一類基于傳輸吞吐量的場景,例如:服務與服務之間資料分發、資料備份等,這類場景一般會采用多點并聯relay來提高傳輸的速度,也是要建立在RUDP上的(這兩點在後面着重來描述)。

資源優化問題:某些場景為了避免TCP的三次握手和四次揮手的過程,會采用RUDP來優化資源的占用率和響應時間,提高系統的并發能,例如:QUIC.

不管哪類場景,都是要保證可靠性,也就是品質,那麼在UDP之上怎麼實作可靠呢?答案就是重傳。

重傳模式

IP協定在設計的時候就不是為了資料可靠到達而設計的,是以UDP要保證可靠,就依賴于重傳,這也就是我們通常意義上的RUDP行為,在描述RUDP重傳之前先來了解下RUDP基本架構,如圖:

RUDP傳輸那些事兒

圖2

RUDP在分為發送端和接收端,每一種RUDP在設計的時候會做不一樣的選擇和精簡,概括起來就是圖中的單元。RUDP的重傳是發送端通過接收端ACK的丢包資訊回報來進行資料重傳,發送端會根據場景來設計自己的重傳方式,重傳方式分為三類:定時重傳,請求重傳和FEC選擇重傳。

定時重傳

定時重傳很好了解,就是發送端如果在發出資料包(T1)時刻一個RTO之後還未收到這個資料包的ACK消息,那麼發送就重傳這個資料包。這種方式依賴于接收端的ACK和RTO,容易産生誤判,主要有兩種情況:

對方收到了資料包,但是ACK發送途中丢失。

ACK在途中,但是發送端的時間已經超過了一個RTO。

是以逾時重傳的方式主要集中在RTO的計算上,如果你的場景是一個對延遲敏感但對流量成本要求不高的場景,就可以将RTO的計算設計比較小,這樣能盡最大可能保證你的延時足夠小。例如:實時操作類網遊、教育領域的書寫同步,是典型的用expense換latency和Quality的場景,适合用于小帶寬低延遲傳輸。如果是大帶寬實時傳輸,定時重傳對帶寬的消耗是很大的,極端情況會用20%的重複重傳率,是以在大帶寬模式下一般會采用請求重傳模式。

請求重傳

請求重傳就是接收端在發送ACK的時候攜帶自己丢失封包的資訊回報,發送端接收到ACK資訊時根據丢包回報進行封包重傳。如下圖:

RUDP傳輸那些事兒

圖3

這個回報過程最關鍵的步驟就是回送ACK的時候應該攜帶哪些丢失封包的資訊,因為UDP在網絡傳輸過程中會亂序會抖動,接收端在通信的過程中要評估網絡的jitter time,也就是rtt_var(RTT方內插補點),當發現丢包的時候記錄一個時刻t1,當t1 + rtt_var < curr_t(目前時刻),我們就認為它丢失了,這個時候後續的ACK就需要攜帶這個丢包資訊并更新丢包時刻t2,後續持續掃描丢包隊列,如果他t2 + RTO <curr_t,再次在ACK攜帶這個丢包資訊,以此類推,直到收到封包為止。這種方式是由丢包請求引起的重發,如果網絡很不好,接收端會不斷發起重傳請求,造成發送端不停的重傳,引起網絡風暴,通信品質會下降,是以我們在發送端設計一個擁塞控制子產品來限流,這個後面我們重點分析。除了網絡風暴以外,整個請求重傳機制也依賴于jitter time和RTO這個兩個時間參數,評估和調整這兩個參數和對應的傳輸場景也息息相關。請求重傳這種方式比定時重傳方式的延遲會大,一般适合于帶寬較大的傳輸場景,例如:視訊、檔案傳輸、資料同步等。

FEC選擇重傳

除了定時重傳和請求重傳模式以外,還有一種方式就是以FEC分組方式選擇重傳,FEC(Forward Error Correction)是一種前向糾錯技術,一般是通過XOR類似的算法來實作,也有多層的EC算法和raptor湧泉碼技術,其實是一個解方程的過程。應用到RUDP上示意圖如下:

RUDP傳輸那些事兒

圖4

在發送方發送封包的時候,會根據FEC方式把幾個封包進行FEC分組,通過XOR的方式得到若幹個備援包,然後一起發往接收端,如果接收端發現丢包但能通過FEC分組算法還原,就不向發送端請求重傳,如果分組内包是不能進行FEC恢複的,就請求想發送端請求原始的資料包。FEC分組方式适合解決要求延時敏感且随機丢包的傳輸場景,在一個帶寬不是很充裕的傳輸條件下,FEC會增加多餘的備援包,可能會使得網絡更加不好。FEC方式不僅可以配合請求重傳模式,也可以配合定時重傳模式。

RTT與RTO的計算

在上面介紹重傳模式時多次提到RTT、RTO等時間度量闡述,RTT(Round Trip Time)即網絡環路延時,環路延遲是通過發送的資料包和接收到的ACK包計算了,示意圖如下:

RUDP傳輸那些事兒

圖5

RTT = T2 - T1

這個計算方式隻是計算了某一個封包時刻的RTT,但網絡是會波動的,這難免會有噪聲現象,是以在計算的過程中引入了權重平均收斂的方法(具體可以參考RFC793)

SRTT = (α * SRTT) + (1-α)RTT

這樣可以求得新逼近的SRTT,在公式總一般α=0.8,确定了SRTT,下一步就是計算RTT_VAR(方差),我們設RTT_VAR = |SRTT – RTT|,那麼SRTT_VAR =(α * SRTT_VAR) + (1-α) RTT_VAR。這樣可以得到RTT_VAR的值,但最終我們是需要去頂RTO,因為涉及到封包重傳,RTO就是一個封包的重傳周期,從網絡的通信流程我們很容易知道,重傳一個包以後,如果一個RTT+RTT_VAR之後的時間還沒收到确定,那我們就可以再次重傳,則可知:

RTO = SRTT + SRTT_VAR。但一般網絡在嚴重抖動的情況下還是會有較大的重複率問題,是以:

RTO = β*(SRTT + RTT_VAR)

1.2 <β<2.0,可以根據不同的傳輸場景來選擇β的值。

RUDP是通過重傳來保證可靠的,重傳在三角平衡關系中其實是用Expense和latency來換取Quality的行為,是以重傳會引來兩個問題,一個是延時,一個是重傳的帶寬,尤其是後者,如果控制不好會引來網絡風暴,是以在發送端會設計一個視窗擁塞機制了避免并發帶寬占用過高的問題。

視窗與擁塞控制

視窗

RUDP需要一個收發的滑動視窗系統來配合對應的擁塞算法來做流量控制,有些RUDP需要嚴格的發送端和接收端的視窗對應,有些RUDP是不要收發視窗嚴格對應。如果涉及到可靠有序的RUDP,接收端就要做視窗就要做排序和緩沖,如果是無序可靠或者盡力可靠的場景,接收端一般就不做視窗緩沖,隻做位置滑動。先來看收發視窗關系圖:

RUDP傳輸那些事兒

圖6

上圖描述的是發送端從發送視窗中發了6個資料封包給接收端,接收端收到101,102,103,106時會先判斷封包的連續性并滑動視窗開始位置到103,,然後每個包都回應ACK,發送端在接收到ACK的時候,會确認封包的連續性,并滑動視窗到103,發送端會再判斷視窗的空餘,然後填補新的發送資料,這就是整個視窗滑動的流程。這裡值的一提的是在接收端收到106時的處理,如果是有序可靠,那麼106不會通知上層業務進行處理,而是等待104,105。如果是盡力可靠和無序可靠場景,會将106通知給上層業務先進行處理。在收到ACK後,發送端的視窗要滑動多少是由自己的擁塞機制定的,也就是說視窗的滑動速度受擁塞機制控制,擁塞控制實作要麼基于丢包率來實作,要麼基于雙方的通信時延來實作,下面來看幾種典型的擁塞控制。

經典擁塞算法

TCP經典擁塞算法分為四個部分:慢啟動、擁塞避免、擁塞處理和快速恢複,這四個部分都是為了決定發送窗和發送速度而設計的,其實就是為了在目前網絡條件下通過網絡丢包來判斷網絡擁塞狀态,進而确定比較适合的發送傳輸視窗。經典算法是建立在定時重傳的基礎上的,如果RUDP采用這種算法來做擁塞控制,一般的場景是為了保證有序可靠傳輸的同時又兼顧網絡傳輸的公平性原則。先逐個來解釋下這幾部分。

慢啟動(slow start)

當連接配接鍊路剛剛建立後,不可能一開始将cwnd設定的很大,這樣容易造成大量重傳,經典擁塞裡面會在開始将cwnd = 1,讓後根據通信過程的丢包來逐漸擴大cwnd來适應目前的網絡狀态,直到達到慢啟動的門限門檻值(ssthresh),步驟如下:

1) 初始化設定cwnd = 1,并開始傳輸資料

2) 收到回饋的ACK,會将cwnd 加1

3) 當一個發送端一個RTT後且未發現有丢包重傳,就會将cwnd = cwnd * 2.

4) 當cwnd >= ssthresh或發生丢包重傳時慢啟動結束,進入擁塞避免狀态。

擁塞避免

當通信連接配接結束慢啟動後,有可能還未到網絡傳輸速度的上線,這個時候需要進一步通過一個緩慢的調節過程來進行适配。一般是一個RTT後如果未發現丢包,就是将cwnd = cwnd + 1。一但發現丢包和逾時重傳,就進入擁塞處理狀态。

擁塞處理

擁塞處理在TCP裡面實作很暴力,如果發生丢包重傳,直接将cwnd = cwnd / 2,然後進入快速恢複狀态。

快速恢複

快速恢複是通過确認丢包隻發生在視窗一個位置的包上來确定是否進行快速恢複,如圖6中描述,如果隻是104發生了丢失,而105,106是收到了的,那麼ACK總是會将ack的base = 103,如果連續3次收到base為103的ACK,就進行快速恢複,也就是将并立即重傳104,而後如果收到新的ACK且base > 103,将cwnd = cwnd + 1,并進入擁塞避免狀态。

經典擁塞控制是基于丢包檢測和定時重傳模式來設計的,在三角平衡關系中是一個典型的以Latency換取Quality的案例,但由于其公平性設計避免了過高的Expense,也就會讓這種傳輸方式很難壓榨網絡帶寬,很難保證網絡的大吞吐量和小時延。

BRR擁塞算法

對于經典擁塞算法的延遲和帶寬壓榨問題google設計了基于發送端延遲和帶寬評估的BBR擁塞控制算法。這種擁塞算法緻力于解決兩個問題:

1. 在一定丢包率網絡傳輸鍊路上充分利用帶寬

2. 降低網絡傳輸中的buffer延遲

BBR的主要政策是就是周期性通過ACK和NACK傳回來評估鍊路的min_rtt和max_bandwidth。最大吞吐量(cwnd)的大小就是:

cwnd = max_bandwidth / min_rtt

傳輸模型如下:

RUDP傳輸那些事兒

圖7

BBR整個擁塞控制是一個探測帶寬和Pacing rate的狀态,有是個狀态:

Startup:啟動狀态(相當于慢啟動),增益參數為max_gain = 2.85

DRAIN:滿負荷傳輸狀态

PROBE_BW:帶寬評估狀态,通過一個較小的BBR增益參數來遞增(1.25)或者遞減(0.75).

PROBE_RTT:延遲評估狀态,通過維持一個最小發送視窗(4個MSS)進行的RTT采樣。

那麼這幾種狀态是怎麼且來回切換的呢?以下是QUIC中BBR大緻的步驟如下:

1) 初始化連接配接時會将設定一個初始的cwnd = 8, 并将狀态設定Startup

2) 在Startup下發送資料,根據ACK資料的采樣周期性判斷是否可以增加帶寬,如果可以,将cwnd = cwnd *max_gain。如果時間周期數超過了預設的啟動周期時間或者發生了丢包,進行DRAIN狀态

3) 在DRAIN狀态下,如果flight_size(發送出去但還未确認的資料大小) >cwnd,繼續保證DRAIN狀态,如果flight_size<cwd,進入PROBE_BW狀态

4) 在PROBE_BW狀态下,如果未發生丢包且flight_size<cwnd * 1.25,将維持原來的cwnd,并進入StartUp,如果發生丢包或者flight_size > cwnd,将cwnd = cwnd * 1.25,如果發生丢包,cwnd = cwnd * .075

5) 在Startup/DRAIN/PROBE_BW三個狀态中,如果持續10秒鐘的通信中沒有出現RTT <= min_rtt,就會進入到PROBE_RTT狀态,并将cwnd = 4 *MSS

6) 在PROBE_RTT狀态,會在收到ACK傳回的時候持續判斷flight_size >= cwnd并且無丢包,将本次統計的最小RTT作為min_rtt,進入Startup狀态。

BBR是通過以上幾個步驟來周期性計算cwnd,也就是網絡最大吞吐量和最小延遲,然後通過pacing rate來确定這一時刻發送端的碼率,最終達到擁塞控制的目的。BBR适合在随機丢包且網絡穩定的情況下做擁塞,如果在網絡信号極不穩定的WIFI或者4G上,容易出現網絡泛洪和預測不準的問題,BBR在多連接配接公平性上也存在小RTT的連接配接比大RTT的連接配接更吃帶寬的情況,容易造成大RTT的連接配接速度過慢的情況。BBR擁塞算法在三角平衡關系中是采用Expense換取latency和Quality的案例。

webRTC gcc

說到音視訊傳輸就必然會想到webRTC系統,在webRTC中對于視訊傳輸也實作了一個擁塞控制算法(gcc),webRTC的gcc是一個基于發送端丢包率和接收端延遲帶寬統計的擁塞控制,而且是一個盡力可靠的傳輸算法,在傳輸的過程中如果一個封包重發太多次後會直接丢棄,這符合視訊傳輸的場景,借用weizhenwei同學一張圖來看個究竟:

RUDP傳輸那些事兒

圖8

gcc的發送端會根據丢包率和一個對照表來pacing rate,當loss < 2%時,會加大傳輸帶寬,當loss >=2% &&loss <10%,會保持目前碼率,當loss >=10%,會認為傳輸過載,進行調小傳輸帶寬.

gcc的接收端是根據資料到達的延遲方差和大小進行KalmanFilter進行帶寬逼近收斂,具體的細節不介紹了,請檢視http://www.jianshu.com/p/bb34995c549a。

這裡值得一說的是gcc引入接收端對帶寬進行KalmanFilter評估是一個非常新穎的擁塞控制思路,如果實作一個盡力可靠的RUDP傳輸系統不失為是一個很好的參考。但這種算法也有個缺陷,就是在網絡間歇性丢包情況下,gcc可能收斂的速度比較慢,在一定程度上有可能會造成REMB很難回報給發送端,容易出現發送端流控失效。gcc在三角平衡關系算一個以Quality和Expense換取latency的案例。

弱視窗擁塞機制

其實在很多場景是不用擁塞控制或者隻要很弱的擁塞控制即可,例如:師生雙方書寫同步、實時遊戲,因為本身的傳輸的資料量不大,隻要保證足夠小的延時和可靠性就行,一般會采用固定視窗大小來進行流控,我們在系統中一般采用一個cwnd =32這樣的視窗來做流控,ACK确認也是通過整個接收視窗資料狀态回報給發送方,簡單直接,也很容易适應弱網環境。

傳輸路徑

RUDP除了優化連接配接、壓榨帶寬、适應弱網環境等以外,它也繼承了UDP天然的動态性,可以在中間應用層鍊路上做傳輸優化,一般分為多點串聯優化和多點并聯優化。我們具體來說一說。

多點串聯relay

在實時通信中一些對業務場景對延遲非常敏感,例如:實時語音、同步書寫、實時互動、直播連麥等,如果單純的服務中轉或者P2P通信,很難無法滿足其需求,尤其是在實體距離很大的情況下。在解決這個問題上SKYPE率先提出全球RTN(實時多點傳輸網絡),其實就是在通信雙方之間通過幾個relay節點來動态智能選路,這種傳輸方式很适合RUDP,我們隻要在通信雙方建構一個RUDP通道,中間鍊路隻是一個無狀态的relay cache集合,relay與relay之間進行路由探測和選路,以此來做到鍊路的高可用和實時性。如下圖:

RUDP傳輸那些事兒

圖9

通過多點relay來保證rudp進行傳輸優化,這類場景在三角平衡關系裡是典型的用expense來換取latency的案例。

多點并聯relay

在服務與服務進行媒體資料傳輸或者分發過程中,需要保證傳輸路徑高可用和提高帶寬并發,這類使用場景也會使用傳輸雙方建構一個RUDP通道,中間通過多relay節點的并聯來解決,如下圖所示:

RUDP傳輸那些事兒

圖10

這種模型需要在發送端設計一個多點路由表探測機制,以此來判斷各個路徑同時發送資料的比例和可以用性,這個模型除了鍊路備份和增大傳輸并發帶寬外,還有個輔助的功能,如果是流媒體分發系統,我們一般會用BGP來做中轉,如果節點與節點之間可以直連,這樣還可以減少對BGP帶寬的占用,以此來減少成本問題。

後記

到這裡RUDP的介紹也就結束了,說了些細節和場景相關的事,也算是個入門級的科普文章。RUDP的概念從提出到現在也差不多有20年了,很多從業人員這希望通過一套完善的方案來設計一個通用的RUDP,我個人覺得這不太可能,就算設計出來了,估計和現在TCP差不多,這樣做的意義不大。RUDP的價值在于根據不同的傳輸場景進行不同的技術選型,可能選擇寬松的擁塞方式、也可能選擇特定的重傳模式,但不管怎麼選,都是基于Expense(成本)、Latency(時延)、Quality(品質)三者之間來權衡,通過結合場景和權衡三角平衡關系RUDP或許能幫助開發者找到一個比較好的方案。

作者:袁榮喜,學霸君資深架構師,16年C程式員,Golang愛好者

本文來自部落格園,作者:老吉來了,轉載請注明原文連結:https://www.cnblogs.com/carlos-zhou/p/15066745.html

繼續閱讀