不知道你有没有想过,我们现在的家庭宽带一般几百上千 Mbps,服务器的带宽可以从几十 Mbps 到几十 Gbps 不等,那么服务器和客户端间怎样「协商」出一个具体的传输带宽?也就是我们跑带宽测试时的结果到底是怎么来的。
其实这背后并不存在一套优雅的设计,说出来甚至可能有点让人大跌眼镜:主要靠丢包。数据发太快了就丢,丢了就发慢点。
比方说服务器带宽限制为了 100Mbps,也就是每秒最多可以发送 100Mbit 的数据,如果我们尝试每秒发送 110Mbit 的数据,那么其中就有 10Mbit 的数据包会被丢弃掉,这就是服务商限制带宽的常见手段。
所以当在下载大量数据时(也就是服务器发包客户端收包),服务器会先从一个比较低的传输速率开始尝试,比方说 10Mbps,发现在这个速率下没有产生丢包,那么就提高到 20Mbps,以此类推,直到到达 100Mbps+ 时,发现出现了丢包,于是服务器判断与客户端间的带宽已基本到达极限,不再尝试提高传输速率。(这里为了便于理解省略了部分细节)
是不是感觉很简单粗暴?但实际上,这个过程中融入了许多策略,如起始速度、每次尝试增加的传输量、如何应对丢包等。这套控制策略被称为拥塞控制算法(Congestion Control Algorithm),也可能被叫做流控算法,其中 Google 开发的 BBR 最为人们所熟知。
由于 TCP 协议必须依靠拥塞控制实现,而拥塞控制算法通常也是为 TCP 协议所研发,所以这也叫做 TCP 拥塞控制。然而,实际上任何一种需要在服务端和客户端间自适应传输带宽的协议都需要拥塞控制算法,因此,你会发现基于 UDP 的 QUIC 协议也有拥塞控制算法的选项,也经常使用 BBR。
聪明的读者读到这可能会产生疑问:
- 这样的话岂不是每次传输时都需要从比较慢的速度起步?
没错,这也常被称为 TCP Slow Start 或者 TCP 爬坡。不同的流控算法表现不同,优秀的算法可以更快的到达峰值。在这方面,BBR 算法表现卓越。
- 如果因网络波动而产生了丢包,而非传输容量达到了上限,这该怎么办?
这是传统拥塞控制算法最常出现的问题,因为这些算法最初是为局域网/有线网络所设计,并没有充分考虑可能出现的网络不稳定情况。而在国际互联网与无线网络中,丢包是常态,并不总是代表网络容量达到上限。举个例子,某传输链路本身可以容纳 100Mbps 的带宽,但固定存在约 1% 的丢包率。理论上,传输速率可以达到 99Mbps,但在传统的拥塞控制算法下的实际表现可能仅为 50Mbps。
BBR 正是为这种现代网络环境设计的,它不仅仅依赖于丢包来调整带宽,在高丢包环境下,其表现明显优于传统算法(例如 Reno、Cubic)。
当然,这种方法仍然是治标不治本的。问题的根本在于流控算法无法区分丢包是由于网络容量问题还是网络波动问题所造成的。因此,Explicit Congestion Notification(ECN)应运而生。
ECN
ECN 的设计其实相当直接:当网络中任意节点即将出现拥塞时(即到达带宽上限),该节点在转发数据包时会给数据包打上一个标记(CE 标记),这样拥塞控制算法就可以依赖这个标记来调节带宽,而非单纯依赖丢包。
但这会要求链路中的所有网络设备均支持 ECN 特性:
如果链路中有不支持 ECN 的设备,那么在达到该设备的容量上限时它不会发出 CE 标记。并且,由于没有办法检测链路上的设备是否都支持 ECN,所以拥塞算法不能完全按照 ECN 标记来调整速率,还需要考虑丢包率。
最差情况下,某些完全不兼容 ECN 的设备会把带有 ECN 标记的数据包判为无效数据包直接丢包,导致无法联通。所以一般 ECN 功能都需要手动开启,或者加入超时回退的策略。
Surge 于最新的 beta 版本中完成了 QUIC 协议的 ECN 支持,可对 TUIC 代理协议手动开启,也可对 Surge Ponte 手动开启。
注:
关于 TCP 协议的 ECN 支持,需要系统内核 TCP 协议栈支持,macOS/iOS 都早已完成了支持,但是在实际测试中却发现永远不能发出带有 ECN 标记的 TCP 包,正在与 Apple 进行沟通确认。
ECN 标记虽然是打在 IP 层的 Header 中,但是这并不表示 ECN 完全是属于 IP 层的特性。原因在于当从 S 往 C 端发送的速率过高产生 CE 标记包后,需要 C 端将这个消息告知 S 端,让 S 端降低发送速率。而这个回馈的过程是在上层协议(如 TCP/QUIC)中实现的。
Hysteria
综上所述,ECN 是一项业界为解决流控问题所提出的标准。但是受限于兼容性问题,可能无法正确生效或者完全无法使用。
Surge 在最近版本中支持的 Hysteria 协议则是尝试以另一种思路解决该问题。即依赖用户手动配置带宽值发送数据,不再考虑丢包情况。
但是这种工作模式让 Hysteria 的流控丧失了自动调整带宽的能力。举个例子,假设家庭带宽为 100Mbps,Hysteria 也配置为了 100Mbps,但是实际使用时,家中的其他设备已经占用了 50Mbps,但 Hysteria 服务器会坚持以 100Mbps 的速率传递数据,那可能会产生两种结果:
- 其中 50% 的流量被固定丢弃,导致传输 100MB 的数据实际耗费了服务器 200MB 的流量。
- 导致家中的其他设备的数据流出现持续性丢包,TCP 拥塞控制不断退让至很低的速度。(Cubic 算法下可能直接归零)
除了其他设备抢占,还有 Wi-Fi/数据网络信号不佳等原因也会让实际带宽低于配置带宽,产生不必要的浪费。
因此使用 Hysteria 协议时应该谨慎,在配置带宽时应留有一定余量,且只在带宽稳定的环境下使用。
注:
- Hysteria 协议也支持使用标准的 BBR 流控,只作为单纯的 QUIC 代理工作,这种情况下也可以开启 ECN 优化。
- 协议作者表示会在未来的版本中加入更多优化,在出现高幅度的固定丢包时予以警告或进行退让。
未来发展
对于带宽协商的问题,ECN 作为解决方式也不算完美,原因在于它只会在拥塞时产生一个单向的简单标记。一是信息量太少,给出更详细的信息可以让发送端更准确的调整速率,二是需要依赖传输层协议的适配去反馈给服务端,破坏了网络分层结构,增加了复杂度。
为此,近些年来仍有各种新规范尝试继续改进该问题,如 Congestion Exposure (ConEx) 、Data Center TCP (DCTCP)、L4S、More Accurate Explicit Congestion Notification (AccECN) 等等。Apple 在这方面非常激进,Darwin Kernel 中经常最先加入各种新网络特性的支持(然而几乎没有文档说明…),但其他厂商和开源社区对这些技术的支持相对缓慢,可能还需要若干年后才能实用。