如何在高丢包率的链路上建立低延迟连接?

这是其实通信领域的话题了,低延迟传输有上百种优化方式,上面说的那些冗余码只是很小一部分,不考虑信道容量的冗余编码系统都是在耍流氓,不用等到同信道内跑两套这样的协议你才会发现问题,一套协议再接近信道带宽容量限制时,就会出现指数上升的丢包率,所以不考虑带宽检测的冗余法就是一个残次品。

要系统的解决低延迟传输问题,需要同时在传输层,协议层,路由层,应用层几个方面着手:

传输层带外冗余:弱智重复法

设你要发送的数据为 x1-xn,你实际发送出去的包为 p1-pn,那么比如 Pn = [Xn, Xn-1, Xn-2],重复前面出现过的 1-2个数据,丢包了你可以随时恢复出来。

传输层带外冗余:异或法

每发四个包 x1-x4,你多发一个冗余包,内容为前面四个包的异或: R = x1 ^ x2 ^ x3 ^ x4,那么本组数据五个数据包(x1-x4, R)中任意丢失一个,都可以从其他四个异或得到。当然,不一定要四个包冗余一个,你可以根据情况和丢包率,两个包或者三个包冗余一个。

传输层带外冗余:解方程法

把每个数据包看成一个整数,要发送 x1-x4 四个包,并不直接发送x,而是将他们进行线性运算得到 y1-y7,然后发送出去:

A1 * x1 + B1 * x2 + C1 * x3 + D1 * x4 = y1   收到
A2 * x1 + B2 * x2 + C2 * x3 + D2 * x4 = y2   收到
A3 * x1 + B3 * x2 + C3 * x3 + D3 * x4 = y3   丢失
A4 * x1 + B4 * x2 + C4 * x3 + D4 * x4 = y4   收到
A5 * x1 + B5 * x2 + C5 * x3 + D5 * x4 = y5   丢失
A6 * x1 + B6 * x2 + C6 * x3 + D6 * x4 = y6   收到
A7 * x1 + B7 * x2 + C7 * x3 + D7 * x4 = y7   丢失

接收方收到一组数据以后,发现y3, y5, y7丢失,得到:

A1 * x1 + B1 * x2 + C1 * x3 + D1 * x4 = y1   
A2 * x1 + B2 * x2 + C2 * x3 + D2 * x4 = y2   
A4 * x1 + B4 * x2 + C4 * x3 + D4 * x4 = y4   
A6 * x1 + B6 * x2 + C6 * x3 + D6 * x4 = y6   

如果 y1-y7 在发送的过程中丢失了三个:y3, y5, y7 观察等式。变量任然有4个,参数也任然有四个A1, A2, A4, A6, B?, C?, D? 四个变量,四个等式,那么我们可以通过解方程求出x1-x4,这就是一个矩阵运算和求逆的过程,也就是俗称的 Read-Solomon 冗余码的基本原理(PS: shorthair/long hair 两个库写的很烂呀),实际传输时需要根据网络质量来选择每组数据和冗余包的比例。

传输层网络评估

需要一套比较强大的系统,实时评估当前网络质量(RTT, 丢包率,抖动值,可用带宽),为协议决策提供参考,比如这时一个带宽很大,延迟却很高的信道呢?还是带宽很小,延迟也很小的信道呢?不同的情况对应不同的策略,当前丢包是常规丢包?震荡性丢包?还是接近信道限制出现无可挽回的丢包?再根据当前协议出于什么情况?交互模式还是单向传输模式,来给出最佳的传输策略。不考虑这些情况的协议,都是比较弱智的(比如楼上提到的几种)。

协议层决策模型

根据不同的情况,来推导不同的数据包丢失后,对整体协议的影响,从而通过马科夫决策过程,来找到哪个包丢失的代价最大,以此来指导冗余包的生成过程:

并且根据网络统计,实时修正决策参数。

协议层响应模型

对 TCP 协议而言,是一个中庸的协议,协议质量的几个要素:公平性,延迟性,带宽利用率。三者不可得兼,然而可以根据你应用情况进行适当调整,牺牲部分公平性和降低部分带宽利用率换取低延迟,比如:

  • RTO翻倍vs不翻倍:TCP超时计算是RTOx2,这样连续丢三次包就变成RTOx8了,十分恐怖,而如果不x2,只是x1.5(实验证明1.5这个值相对比较好),可以很好提高传输速度。
  • 选择性重传 vs 全部重传:TCP丢包时会全部重传从丢的那个包开始以后的数据,我们可以用选择性重传,只重传真正丢失的数据包。
  • 快速重传:发送端发送了1,2,3,4,5几个包,然后收到远端的ACK: 1, 3, 4, 5,当收到ACK3时,我们知道2被跳过1次,收到ACK4时,知道2被跳过了2次,此时可以认为2号丢失,不用等超时,直接重传2号包,可以大大改善了丢包时的传输速度。
  • 延迟ACK vs 非延迟ACK:TCP为了充分利用带宽,延迟发送ACK(NODELAY都没用,有些系统可以微调,但是是改注册表全局值,不太好),这样超时计算会算出较大 RTT时间,延长了丢包时的判断过程,我们对ACK是否延迟发送可以实时调节(也可以根据交互模式还是单向传输模式来抉择,或者给用户开关选择)。
  • UNA vs ACK+UNA:ARQ模型响应有两种,UNA(此编号前所有包已收到,如TCP)和ACK(该编号包已收到),光用UNA将导致全部重传,光用ACK则丢失成本太高,以往协议都是二选其一,而实现一套更好的协议,除去单独的 ACK外,可以让所有包都有UNA信息,得到更好效果。
  • 流控改进:传统 TCP 都是基于退让的流控,现代 TCP 不停的再改退让法则,其实限制于 TCP 的兼容性他们才跳不出来这个坑,如果完全抛开 TCP 协议,各种现代传输协议都是使用前面提到的带宽估计和网络质量评估来代替流控的。
  • 窗口初始大小:用不着象传统 TCP 协议那样死板,每次建立新连接或者远端窗口探测的过程都让窗口设置成1,可以直接设置成更高值了。
    协议层优化结合前面的冗余和信道质量评估,跳出 TCP 的限制,以牺牲部分公平性和降低部分带宽利用率的代价,可以得到更低的传输延迟。

协议层优化结合前面的冗余和信道质量评估,跳出 TCP 的限制,以牺牲部分公平性和降低部分带宽利用率的代价,可以得到更低的传输延迟。

这部分我开源了,欢迎访问:skywind3000/kcp

路由层优化:路由实时评估

公网提供的路由并不是最佳的,书上虽然说路由就是要找最优通路,实际情况是,最优通道价格高,屌丝用户就走走免费的省道国道得了,高富帅再走高速。因此你需要建立自己的全局路由系统。对全局网络有一个比较明确的评估。

路由层优化:路由动态选择

根据前面的实时路由评估结果,使用空闲用户帮忙转发以及 IDC 布点的模式,决策最佳的传输路径,并且考虑线路冗余(同一个时刻保持两条以上的通路,一条挂了实时切换到另外一条)。每两三个路由节点之间,采用三角连接法,每个节点至少保证两条线路:

不要以为网络上地理位置越近的传输速度越快,也不要以为跳数越少传输速度越快,实际情况是,正确的选择路由,即便跳数增加,也能极大的提升传输速度。

物理层传输优化

是用更好的传输介质(比如光纤),或者增加传输功率,又或者再功率不变的情况下使用低频信号(但是低频信道带宽一般不高),都能降低信号损失。一般来讲,可以通过评估信号衰减来自动增加减少功率,实时切换使用高低频信道。或者更换天线或者信号源位置让衰减最低(好像有点扯远了)。。。

使用吞吐量更大的设备,同样成本下一般带宽,每秒发包数,RTT三者不可得兼,根据情况选择擅长 RTT 或者带宽的设备。

应用层带内冗余:低频数据冗余

比如你在传送一段音频的话,比如每秒 40 个数据帧,用类似 MP3 的压缩方法,那么你可以将每秒的音频分作 40 段,每段 25ms,然后进行 DCT ,滤波,量化,无损压缩等几个传统步奏后,得到一段类似 mp3 的25ms的数据,我们叫一帧,发包的时候,除了包含这一帧的数据外,还同时包含上几帧的低频数据,这样对带宽增加不大,然而我们再解码的时候,如果发现丢包就可以用其他包含有的本帧低频数据,还原出一个质量没有原来好,但是听起来差不多的数据,这叫低频数据冗余。

应用层带内冗余:DSP处理

这是更高级的处理方式,比如你在传输视频,视频中最费带宽的是 I 帧数据,即便 H.265,I帧数据比较其他帧也是很大的。然而 H.264, H.265 系列并没有很好的考虑容错和抗损坏。我们可以用帮他们在这方面做的更好一些,才用频域交错存储及其他一些方法,比如把一幅质量为 70% 大小为 32KB 的 I 帧图像,存储成两张质量为 40% 大小为 16KB – 18KB 的 I 帧图像,两个拆分后的图像单独看,都是一副质量更低的图片,但是他们组合起来,却能变成一张质量很好的图片。这样对于传输视频而言,I帧的瓶颈就不那么明显了,即时丢了一半,也只是损失一下效果而已。

修改 H.264/ H.265的方法有很多,上面只是举个例子。如果你传送的是视频数据的话,你可以找一找相关的文章,教你改进 H.264, H.265。同样如果你传 AAC的话,也有很多方法在带内实现冗余。

上面说的各种带外冗余主要是要 “增加带宽”, 而应用层的 “带内冗余” 主要思路是丢包了就降低质量,在带宽收到限制的时候,我们往往需要果断的使用各种带内冗余法,来保证较流畅的传输体验。

应用层误差掩盖

传送视频数据发现局部丢失了,可以用前后两帧画面以及周围的图块来推断丢失的图块可能是什么,然后模拟生成一下,要比直接马赛克好很多。

传送音频数据发现局部丢失了,可以用前后音频频域插值模拟出丢失的声音片段,也可以通过学习的方式,通过前面历史数据的基音和共振峰提取,来模拟丢失的声音。

即便不模拟,还有方法是通过生成一段舒适的背景噪音来填充丢失的片段,这样会比直接空白音好很多,或者通过不改变基音却变慢语速的方式,让前后两段声音接起来,这样你听着就是声音慢了几十毫秒,但是很连续,发现不了其中丢失。

比如你传送游戏数据的话,一般位移数据可以用非可靠传输方式来传输,每组移动数据不但包含坐标和方向,还要包括时间和速度,这样,数据丢失了,你从后面的数据恢复出来以后,画面上做一个移动差值(导航推测),立马就能掩盖这次传输误差。

所谓应用层误差掩盖,是根据你的应用特点来设计规则,即便传输数据丢失,你也能通过各种模拟和插值的方式掩盖过去,只要保证后来的数据能够修正即可,这样即便信号不好,出现短时误差了,也能得到即时的纠正。

应用层延迟控制

数据包前后顺序依赖(Pn依赖 Pn-1)可以进化为交错依赖(Pn依赖 Pn-2,Pn+1 依赖 Pn-1),这样丢包发生只会导致一部分后续数据无法工作,而不会导致某一时间内所有后续数据没法工作。

控制缓存策略,缓存小了延迟低但是效率也低,缓存大了延迟高,效率也高。网络拥塞了缓存怎么办?使用更少的往返次数来实现交流。

。。。。。。

总结

所谓 “降低延迟”,其实就是 “提高通信质量”,这个问题通信行业一直在研究解决的事情,不可能靠单方面的技巧提高很多(比如冗余),这是一个系统工程,有它的理论体系,有它的实践方法,还有各种注意事项。

Loading

About skywind

Putty 本无树,MinGW 亦非台
This entry was posted in 网络编程 and tagged , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *