前面的两篇文章《谈谈网络通信中的 ACK、NACK 和 REX》和《谈谈网络通信中的 FEC 基础》介绍了网络通信中的丢包重传和 FEC 的相关理论和方法,他们都是在网络发生丢包的情况下的补救措施,本文则往前进一步,介绍下如何通过流量整形技术,尽可能地避免网络发生丢包。
从堵车说起
堵车的原因有很多种,我们先聊聊图中的这种:假设某公路是 3 股道,也就是说,每时刻能同时进入该公路的车辆并行是 3 辆,那么,如果某一时间段,同时试图进入该公路的车辆超过 3 辆,则必然会出现由于公路的承载能力不够带来的 “堵车”。
网络传输也类似,假设网络带宽是 2Mbps,如果在每秒硬塞给网络的数据包 > 2Mbps,网络通道也会受不了。不过它的表现形式与公路不一样的地方在于:超出网络承载能力的数据包,可能会被网卡/路由器/交换机给丢掉,即我们常说的丢包(loss)。
对于网络传输而言,丢包带来的成本是很高的,因为一些重要的数据包丢失后,是需要 “重传” 的,而 “重传” 太多,来回反复会增加了数据传输的延时,也会进一步恶化网络负荷,最终极大地降低了传输的效率。
因此,我们需要从根本上尽可能地减少 “丢包” 的产生,简单来说,就是控制单位时间内送入网络传输的数据量,尽量平滑且不要超过网络带宽承载能力。
控制对象
既然要控制送入网络传输的数据量,就得先找到数据是怎么产生的,又是在通过哪个环节送入到网络的。
如图,音视频传输的数据 “源头” ,无外乎就是本地的音视频文件、麦克风采集的音频流、摄像头采集的视频流、桌面采集到的屏幕流等,它们经过编码压缩和封包处理,然后经过 “发送模块” 送入到网络中。
设置和修改采集的配置(如:分辨率、帧率)、编码器的配置(如:GOP 间隔、码率)等,是可以减少实时产生的总数据量的,但是数据的产生并不是 “平滑” 的,特别是视频流/屏幕流,画面的突变,会带来数据量的突变,因此,送入到 “发送模块” 的数据量,也并不会总是 “平滑” 的。
从上面的 “堵车” 理论,为了避免丢包,我们需要尽可能地将数据 “平滑” 地送入网络中,因此,“流量整形” 在此派上了用场,它作用于 “发送模块”,目标是调整数据传输的平均速率,防止突发性的流量暴增导致网络拥塞和丢包。
流量整形
如何平输入和输出,一个最容易想到方法,就是增加 “缓冲”,让输入的数据先进入 “缓冲区” ,然后用恒定的 “速率” 从缓冲区取数据输出。这种方法称之为 “漏桶算法”。
漏桶算法(Leaky Bucket)
如图,使用一个 packet buffer(漏桶),把所有输入的 packet 缓存起来。
设置一个目标的输出码率(比如:3Mbps),固定的时间间隔(比如:10ms),读取 packet buffer(漏桶)中固定数量的 packet(如:3Mbit * 10ms / 1000 = 0.03Mbit)进行网络发送。注:如果某时刻缓冲区没有数据,则不用发送了。
漏桶算法的缺点
漏桶算法有一个明显的缺点,因为非常精准的网络带宽无法预判,那么假设你设置了一个比较小的目标码率(如 3Mbps),可能小于真实的网络带宽(如 10Mbps),这时,如果业务上产生了一些突发的流量,真实的网络带宽本可以允许更快地完成发送,但经过了漏桶算法后,依然会以恒定的目标码率慢慢地发送。所以说,漏桶算法无法充分用满网络资源来降低传输延时。
解决方案有 2 个:
-
先慢启动,然后将漏桶的目标码率持续上探,直到出现网络恶化(如:丢包增多)后再降下来,如此反复,维持一个动态平衡,使得漏桶算法的目标码率持续无限逼近网络的承载能力
-
改进漏桶算法,允许其在执行过程中,偶尔出现一些超过预设平均值的突发传输能力,用于应对业务上的流量突发,即:令牌桶算法
令牌桶算法(Token Bucket)
令牌桶算法在漏桶算法基础上,提出一个改进,就是新增了 “令牌” 和 “令牌桶”。
“令牌” 代表着允许传输的字节数量,我们以固定的时间间隔(比如:10ms)产生并送入 “令牌” 到 “令牌桶”,“令牌桶” 设置一个 “令牌” 数量上限(满了没有消耗就丢掉新增的令牌),因此,一次传输最大的允许突增的字节数 = M x B
发送模块,以固定 t ms 的时间间隔去读取 packet buffer,读取的字节数 X 必须接近但小于等于当前 “令牌桶” 中允许传输的字节数(即:“令牌桶” 里剩余的 “令牌个数” x B)。并且,传输完了多少字节,则删除掉 “令牌桶” 里对应个数的 “令牌”。
这种方法解决突发流量的关键点在哪呢 ?
在于 “令牌” 是可以积累的,可能缓冲区在前 N ms 都没有突发的数据,这时,“令牌” 依然在产生,并且被积累在了 “令牌桶”,一旦缓冲区突增了大量的数据,则可以在短时间内快速消费掉。当然,为了防止突破网络承载能力导致丢包,“令牌桶” 的最大 “令牌数量” 也相应做了一些限制。