其他分享
首页 > 其他分享> > UDP可靠传输之重传策略

UDP可靠传输之重传策略

作者:互联网

IP 协议在设计的时候就不是为了数据可靠到达而设计的,所以 UDP 要保证可靠,就依赖于重传,目前共有三总方式:
1.定时重传
发送端如果在发出数据包(T1)时刻一个 RTO 之后还未收到这个数据包的 ACK 消息,那么发送端就重传这个数据包。这种方式依赖于接收端的 ACK 和 RTO,容易产生误判,主要有两种情况:
1)对方收到了数据包,但是 ACK 发送途中丢失;
2)ACK 在途中,但是发送端的时间已经超过了一个 RTO。
所以超时重传的方式主要集中在 RTO 的计算上,如果你的场景是一个对延迟敏感但对流量成本要求不高的场景,就可以将 RTO 的计算设计得比较小,这样能尽最大可能保证你的延时足够小。
例如:实时操作类网游、教育领域的书写同步,是典型的用 expense 换 latency 和 quality 的场景,适合用于小带宽低延迟传输。如果是大带宽实时传输,定时重传对带宽的消耗是很大的,极端情况会有 20% 的重传率,所以在大带宽模式下一般会采用请求重传模式。
2.请求重传
请求重传就是接收端在发送 ACK 的时候携带自己丢失报文的信息反馈,发送端接收到 ACK 信息时根据丢包反馈进行报文重传。
在这里插入图片描述这个反馈过程最关键的步骤就是回送 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 会增加多余的包,可能会使得网络更加不好。FEC 方式不仅可以配合请求重传模式,也可以配合定时重传模式。
4.RTT 与 RTO 的计算
RTT(Round Trip Time)即网络环路延时,环路延迟是通过发送的数据包和接收到的 ACK 包计算的,示意图如下:
在这里插入图片描述RTT = T2 - T1:
这个计算方式只是计算了某一个报文时刻的 RTT,但网络是会波动的,这难免会有噪声现象,所以在计算的过程中引入了加权平均收敛的方法。
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,可以根据不同的传输场景来选择β的值。
5.窗口与拥塞控制
5.1窗口
通过滑动窗口系统来配合对应的拥塞算法做流量控制,可以有效避免网络风暴,如果涉及到可靠有序的 UDP,接收端就要做窗口排序和缓冲,如果是无序可靠或者尽力可靠的场景,接收端一般不做窗口缓冲,只做位置滑动。
在这里插入图片描述
上图描述的是发送端从发送窗口中发了 6 个数据报文给接收端,接收端收到 101,102,103,106 时会先判断报文的连续性并滑动窗口开始位置到 103,接着每个包都回应 ACK,发送端在接收到 ACK 的时候,会确认报文的连续性,并滑动窗口到 103,发送端会再判断窗口的空余,然后填补新的发送数据,这就是整个窗口滑动的流程。

这里值的一提的是在接收端收到 106 时的处理,如果是有序可靠,那么 106 不会通知上层业务进行处理,而是等待 104、105。如果是尽力可靠和无序可靠场景,会将 106 通知给上层业务先进行处理。在收到 ACK 后,发送端的窗口要滑动多少是由自己的拥塞机制定的,也就是说窗口的滑动速度受拥塞机制控制,拥塞控制实现要么基于丢包率来实现,要么基于双方的通信时延来实现,下面来看几种典型的拥塞控制。
5.2经典拥塞算法
TCP 经典拥塞算法分为四个部分:慢启动、拥塞避免、拥塞处理和快速恢复,这四个部分都是为了决定发送窗口和发送速度而设计的,其实就是为了在当前网络条件下通过网络丢包来判断网络拥塞状态,从而确定比较适合的发送传输窗口。
经典算法是建立在定时重传的基础上的,如果UDP 采用这种算法来做拥塞控制,一般的场景是为了保证有序可靠传输的同时又兼顾网络传输的公平性原则。先逐个来解释下这几部分。
慢启动(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,然后进入快速恢复状态。

快速恢复:
通过确认丢包只发生在窗口一个位置的包来确定是否进行快速恢复,如前图描述,如果只是 104 发生了丢失,而 105 和 106 是收到了的,那么 ACK 总是会将 ACK 的 base = 103,如果连续 3 次收到 base 为 103 的 ACK,就进行快速恢复,也就是立即重传 104,而后如果收到新的 ACK 且 base > 103,将 cwnd = cwnd + 1,并进入拥塞避免状态。
经典拥塞控制是基于丢包检测和定时重传模式来设计的,是一个典型的以 latency 换取 quality 的案例,但由于其公平性设计避免了过高的 expense,也就会让这种传输方式很难压榨网络带宽,很难保证网络的大吞吐量和小时延。
9.3 BBR 拥塞算法
对于经典拥塞算法的延迟和带宽压榨问题 Google 设计了基于发送端延迟和带宽评估的 BBR 拥塞控制算法。
这种拥塞算法致力于解决两个问题:
1)在一定丢包率网络传输链路上充分利用带宽;
2)降低网络传输中的 buffer 延迟。
BBR 的主要策略是周期性通过 ACK 和 NACK 返回来评估链路的 min_rtt 和 max_bandwidth。最大吞吐量(cwnd)的大小就是:cwnd = max_bandwidth / min_rtt。
BBR 整个拥塞控制是一个探测带宽和 Pacing rate 的状态,有 4 个状态:
1)Startup:启动状态(相当于慢启动),增益参数为 max_gain = 2.85;
2)DRAIN:满负荷传输状态;
3)PROBE_BW:带宽评估状态,通过一个较小的 BBR 增益参数来递增(1.25)或者递减 (0.75);
4)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 * 0.75;
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 适合在随机丢包且网络稳定的情况下做拥塞,如果在网络信号极不稳定的 Wi-Fi 或者 4G 上,容易出现网络泛洪和预测不准的问题,BBR 在多连接公平性上也存在小 RTT 的连接比大 RTT 的连接更吃带宽的情况,容易造成大 RTT 的连接速度过慢的情况。BBR 拥塞算法是采用 expense 换取 latency 和 quality 的案例。
9.4 WebRTC GCC
在 WebRTC 中对于视频传输也实现了一个拥塞控制算法 (GCC),WebRTC 的 GCC 是一个基于发送端丢包率和接收端延迟带宽统计的拥塞控制,而且是一个尽力可靠的传输算法,在传输的过程中如果一个报文重发太多次后会直接丢弃,这符合视频传输的场景。
在这里插入图片描述GCC 的发送端会根据丢包率和一个对照表来 pacing rate,当 loss < 2% 时,会加大传输带宽,当 loss >=2% &&loss <10%,会保持当前码率,当 loss>=10%,会认为传输过载,进行调小传输带宽。
GCC 的接收端是根据数据到达的延迟方差和大小进行 KalmanFilter 评估,进行带宽逼近收敛。
这种算法也有个缺陷,就是在网络间歇性丢包情况下,GCC 可能收敛的速度比较慢,在一定程度上有可能会造成 REMB 很难反馈给发送端,容易出现发送端流控失效。
9.5 弱窗口拥塞机制
其实在很多场景是不用拥塞控制或者只要很弱的拥塞控制即可,例如:师生双方书写同步、实时游戏,因为本身的传输的数据量不大,只要保证足够小的延时和可靠性就行,一般会采用固定窗口大小来进行流控,我们在系统中一般采用一个 cwnd =32 这样的窗口来做流控,ACK 确认也是通过整个接收窗口数据状态反馈给发送方,简单直接,也很容易适应弱网环境。

标签:UDP,重传,RTT,ACK,发送,传输,拥塞,cwnd
来源: https://blog.csdn.net/weixin_50763382/article/details/120333452