前言
RUDP
,可靠用户数据包协议,是一种基于可靠数据协议(RDP)的简单分组传输协议。
UDP/IP协议中,RUDP是分层的并为虚拟连接提供可靠有序发送(直到重新发送的最大数目)。RUDP是便于多个传输层使用。其提供一组数据服务质量增强机制,如拥塞控制的改进、重发机制及淡化服务器算法等,从而再包丢失和网络拥塞的情况下,RTP客户机面前呈现的就是一个高质量的RTP流。在不干扰协议的世事特征的同时,可靠UDP的拥塞控制机制允许TCP方式下的流控制行为。
一、RUDP应用场景
1、端对端连通性问题
一般终端直接和终端通信都涉及NAT穿越,TCP
在NAT
穿越实现非常困难,相对来说UDP穿越NAT却简单很多,如果是端到端的可靠通信一般用RUDP
方式解决。
场景有:端到端的文件传输、音视频传输、交互指令传输等等。
2、弱网环境传输问题
在一些WIFI
或者3G/4G
移动网下,需要做低延迟可靠通信,如果用TCP通信延迟可能会非常大,这会影响用户体验。
场景有:实时的操作类网游通信、语音对话、多方白板书写等。
3、传输路径优化问题
在一些对延时要求很高的场景下,会用应用层relay
的方式来做传输路由优化,也就是动态智能选路,这时双方采用RUDP方式来传输,中间的延迟进行relay选路优化延时。还有一类基于传输吞吐量的场景,例如:服务与服务之间数据分发、数据备份等,这类场景一般会采用多点并联relay
来提高传输的速度,也是要建立在RUDP上的。
4、资源优化问题
某些场景为了避免TCP的三次握手和四次挥手的过程,会采用RUDP来优化资源的占用率和响应时间,提高系统的并发能力,如:QUIC。
二、RUDP保证可靠性的机制
RUDP保证可靠性,就是使用重传
RUDP的重传方式有三类:定时重传、请求重传和FEC选择重传
1、定时重传
定时重传很好理解,就是发送端如果在发出数据包(T1)时刻一个RTO之后还没有收到这个数据包的ACK消息,那么就会重发该数据包。这种方式依赖于接收端的ACK和RTO。
这种方式依赖于接收端的ACK和RTO,容易产生误判,主要有两种情况:
- 对方收到了数据包,但是ACK发送途中丢失
- ACK在途中,但是发送端的时间已经超过了一个RTO。
所以超时重传的方式主要集中在RTO的计算中,如果你的场景是一个对延迟敏感但对流量成本要求不高的场景,就可以将RTO的计算设计比较小,这样能尽最大可能呢保证你的延迟足够小。如:实时操作类网游、教育领域的书写同步,是典型的用expense换latency和quality的场景,适合用于小带宽低延迟传输。如果是大带宽实时传输,定时重传对带宽的消耗是很大的,极端情况会用20%的重复重传率,所以在大带宽模式下一般采用请求重传模式。
2、请求重传
请求重传就是接收端的发送ACK
时携带自己丢失报文的信息,因为UDP在网络传输过程中会乱序会抖动。
接收端在通信的过程中要评估网络的jitter time,也就是rtt_var(RTT方差值),当发现丢包的时候记录一个时刻t1
,当t1 + rtt_var < curr_t
(当前时刻),我们就认为它丢失了,这个时候后续的ACK
就需要携带这个丢包信息并更新丢包时刻t2
,后续持续扫描丢包队列,如果他t2 +RTO < curr_t,再次在ACK携带这个丢包信息,依次类推,知道收到报文为止。
这种方式是由丢包请求引起的重发,如果网络很不好,接收端会不断发起重传请求,造成发送端不停的重传,引起网络风暴,通信质量会下降,所以我们再发送端设置一个拥塞控制模块来限流,这个后面我们重点分析。
除了网络风暴以外,整个请求重传机制也依赖于jitter time
和RTO
这两个时间参数,评估和调整这两个参数和对应的传输场景也息息相关。
请求重传这种方式比定时重传方式的延迟会大,一般适合于带宽较大的传输场景,如:视频、文件传输和数据同步等。
3、FEC选择重传
除了定时重传和请求重传模式外,还有一种方式就是以FEC分组方式选择重传,FEC
(Forward Error Correction)是一种前向纠错技术,一般是通过XOR
类似的算法实现,也有多层的EC算法和raptor涌泉码技术,其实就是一个解方程的过程。
在发送方发送报文时,会根据FEC方式把几个报文进行FEC分组,通过XOR的方式得到若干个冗余包,然后一起发往接收端,如果接收端发现丢包但能通过FEC分组算法还原,就不向发送端请求重发,如果分组内包是不能进行FEC恢复,则请求发送端发送原始的数据包。
FEC分组方式适合要求延迟敏感且随机丢包的传输场景,在一个带宽不是很充裕的条件下,FEC会增加多余的冗余包,可能会使得网络更加不好。FECC方式不仅可以配合请求重传模式,也可以配合定时重传模式。
三、涉及的相关知识
1、RTT和RTO的计算
在上面介绍重传模式时对此提到RTT、RTO等时间度量阐述,RTT(Round Trip Time)即网络环路延迟,环路延迟是通过发送的数据包和接受到的ACK包计算,示意图如下:
RTT = T2 - T1
这个计算方式只是计算了某一个报文时刻的RTT,但网络是会波动的,这难免会有噪声现象,所以在计算的过程中引入了加权平均收敛的方法。
SRTT = α * SRTT + (1 - α) * RTT
(第一次的 SRTT = RTT
)
这样可以求得新逼近的SRTT,在公式中一般α = 0.8,
确定了SRTT
,下一步就是计算RTT_VAR
(方差),
设RTT_VAR = | SRTT - RTT|
则SRTT_VAR = α * SRTT_VAR + (1 - α) * RTT_VAR
这样可以得到RTT_VAR的值,但最终需要取计算RTO的值,
因为涉及到报文重传,从网络的通信流程我们很容易知道,重传一个包以后,如果一个RTT + RTT_VAR 之后的时间还没有收到确定,那我们就可以再次重传,则可知:
RTO = SRTT + SRTT_VAR
但一般网络在严重抖动的情况下,还是会有很大的重复率问题,所以
RTO = β * (SRTT + RTT_VAR) 1.2 < β < 2.0
RUDP
是通过重传来保证可靠的,重传在三角平衡关系中其实是用Expense和Latency来换取QUality的行为,所以重传会引来两个问题,一是延迟,一个是重传的带宽,尤其是后者,控制不好会引起网络风暴,所以在发送端会设计一个窗口拥塞控制来避免并发带宽占用过高的问题。
二、窗口与拥塞控制
1、窗口
RUDP需要一个收发的滑动窗口系统来配合对应的拥塞算法来做流量控制,有些RUDP需要严格的发送端和接收端的窗口对应,有些RUDP是不要收发窗口严格对应。
如果涉及到可靠有序的RUDP,接收端要做窗口就要做排序和缓冲,如果是无序可靠或者尽力可靠的场景,接收端一般就不作窗口缓冲,只做位置滑动。
2、经典的拥塞算法
TCP
经典拥塞算法分为四部分:慢启动、拥塞避免、拥塞处理和快速恢复
若RUDP采用这个算法来做拥塞控制,一般的场景是为了保证有序可靠传输的同时又兼顾网络传输的公平性原则。
- 慢启动
步骤如下:
(1)初始化设置cwnd = 1
,并开始传输数据
(2)收到反馈的ACK,会将cwnd 加 1
(3)当一个发送端在一个RTT后没有发现有丢包重传,就会将cwnd = cwnd * 2
(4)当cwnd >= ssthresh(门限阈值)或发生丢包重传时慢启动结束,进入拥塞避免状态。
- 拥塞避免
当通信连接结束慢启动后,有可能还未到网络传输速度的上线,这个时候需要进一步通过一个缓慢的调节过程进行适配。一般是一个RTT后如果未发现丢包,将cwnd = cwnd + 1。一旦发现丢包和超时重传,就进入拥塞处理状态。
- 拥塞处理
拥塞处理在TCP里面是实现很暴力,如果发现丢包重传,直接将cwnd = cwnd / 2
,然后进入快速恢复状态。
- 快速恢复
快速恢复是通过确认丢包只发生在窗口一个位置的包上来确定是否进行快速恢复。通过线程增加哎恢复。
2、BRR拥塞算法
对于经典拥塞算法的延迟和带宽压榨问题,google设计了基于发送端延迟和带宽评估的BBR拥塞控制算法。
这种拥塞算法致力于解决两个问题:
(1)在一定丢包率网络传输链路上充分利用带宽
(2)降低网络传输中的buffer延迟
BBR的主要策略就是周期性通过ACK和NACK返回来评估链路的min_rtt和max_bandwidth。最大吞吐量(cwnd)的大小是:
cwnd = max_bandwidth / min_rtt
传输模型如下:
步骤如下:
BBR整个拥塞控制是给探测带宽和Pacing rate的状态,有是个状态:
Startup
:启动状态(相当与慢启动),增益参数为max_gain = 2.84
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 < cwnd
,则进入PROBE_BW
状态。
(4)在PROBE_BW
状态下,
- 如果未发生丢包且
flight_size < cwnd * 1.25
,将维持原来的cwnd,并进入StartUp
状态, - 如果未发生丢包或者
flight_size > cwnd
,将cwnd = cwnd * 1.25
, - 如果发生丢包,则
cwnd = cwnd * 0.75
(5)在StartUp / DRAIN / PROBE_BW三种状态下,如果持续10s的通信中没有出现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在多连接公平性上也存在小RRT的连接比大RTT的连接更吃带宽的情况,容易造成大RTT的连接速度过慢的情况。BBR拥塞算法在三角平衡关系中是采用Expense换取latency和Quality的案例
3、webRTC gcc
音视频传输就必然会想到webRTC系统,在webRTC总对于视频传输也实现了一个拥塞控制算法(gcc),webRTC
的gcc
是一个基于发送端丢包率和接收端延迟带宽统计的拥塞控制,而且是一个尽力可靠的传输算法。
在传输的过程中如果一个报文重发多次后会直接丢弃,这符合视频传输的场景。
该拥塞控制算法在三角平衡关系中是一个以Quality和Expense换取latency的案列
4、弱窗口拥塞控制
其实在很多场景是不用拥塞控制或者只要很弱的拥塞控制即可,如:师生双方书写同步、实时游戏,因为本身的传输的数据量不大,只要确保足够小的延迟和可靠性
即可,一般是采用固定窗口大小来进行流控,我们在系统中一般采用一个cwnd = 32
这样的窗口来流控,ACK确认也是通过整个接收窗口反馈给发送方,简单直接、也很容易适应弱网环境。
四、传输路径
RUDP除了优化连接、压榨带宽、适合弱网环境等外,它还继承了UDP天然的动态性,可以在中间应用层链路上做传输优化,一般分为多点串联优化和多点并联优化。具体如下:
1、多点串联relay
在实时通信中一些业务场景对延迟非常敏感,如:实时语言、同步书写、实时互动、直播连麦等,如果单纯的服务中转或者P2P通信,很难无法满足其需求,尤其是在物理距离很大的情况下。
在解决这个问题上SKY PE率先提出全球RTN(实时多点传输网络),其实就是在通信双方之间通过几个relay节点来动态智能选路,这种传输方式很适合RUDP,我们只要在通信双方构建一个RUDP通道,中间链路只是一个无状态的relay cache集合,relay和relay之间进行路由探测和选路,以此来做到链路的高可用性和实时性。如下:
通过多点relay来保证RUDP进行传输优化、这类场景在三角平衡关系是典型的用expense换取latency的案例。
2、多点并联relay
在服务和服务进行媒体数据传输或者分发过程中,需要保证传输路径高可用和提高带宽并发,这类使用场景也会使用传输双方构建一个RUDP通道,中间通过多relay节点的并联来解决,如下如:
这种模型需要在发送端设计一个多点路由表探测机制,以此来判断各个路径同时发送数据的比例和可用性,这个模型除了链路备份和增大传输并发带宽外,还有个辅助功能,如果是流媒体分发系统,一般会用BGP来做中转,如果是节点和节点之间可以直连,这样还可以减少对BGP带宽的占用,以此来减少成本问题。
来源:RUDP、RUDP传输那些事儿