netdev 第二天:从网络代码中移除“尽可能快”这个目标

2018-08-20 13:00


嗨!今天是 netdev 会议的第 2 天,我只参加了早上的会议,但它非常有趣。今早会议的主角是 Van Jacobson 给出的一场名为 “从尽可能快中变化:教网卡以时间”的演讲,它的主题是关于互联网中拥塞控制的未来!!!

下面我将尝试着对我从这次演讲中学到的东西做总结,我几乎肯定下面的内容有些错误,但不管怎样,让我们开始吧!

这次演讲是关于互联网是如何从 1988 开始改变的,为什么现在我们需要新的算法,以及我们可以怎样改变 Linux 的网络栈来更容易地实现这些算法。

什么是拥塞控制?

在网络上的任何成员总是无时无刻地发送信息包,而在互联网上的连接之间有着极其不同的速度(某些相比其他极其缓慢),而有时候它们将被塞满!当互联网的一个设备以超过它能处理的速率接收信息包时,它将丢弃某些信息包。

你所能想象的最天真的发送信息包方式是:

  1. 将你必须发送的信息包一次性发送完。
  2. 假如你发现其中有的信息包被丢弃了,就马上重新发送这些包。

结果表明假如你按照上面的思路来实现 TCP,互联网将会崩溃并停止运转。我们知道它会崩溃是因为在 1986 年确实发生了崩溃的现象。为了解决这个问题,专家发明了拥塞控制算法 —— 描述如何避免互联网的崩溃的原始论文是 Van Jacobson 于 1988 年发表的 拥塞避免与控制(30 年前!)。

从 1988 年后互联网发生了什么改变?

在演讲中,Van Jacobson 说互联网的这些已经发生了改变:在以前的互联网上,交换机可能总是拥有比服务器更快的网卡,所以这些位于互联网中间层的服务器也可能比客户端更快,并且并不能对客户端发送信息包的速率有多大影响。

很显然今天已经不是这样的了!众所周知,今天的计算机相比于 5 年前的计算机在速度上并没有多大的提升(我们遇到了某些有关光速的问题)。所以我想路由器上的大型交换机并不会在速度上大幅领先于数据中心里服务器上的网卡。

这听起来有些糟糕,因为这意味着客户端更容易在中间层的连接中达到饱和,而这将导致互联网变慢(而且 缓冲膨胀 将带来更高的延迟)。

所以为了提高互联网的性能且不让每个路由上的任务队列都达到饱和,客户端需要表现得更好并且在发送信息包的时候慢一点。

以更慢的速率发送更多的信息包以达到更好的性能

下面的结论真的让我非常意外 —— 以更慢的速率发送信息包实际上可能会带来更好的性能(即便你是在整个传输过程中,这样做的唯一的人),下面是原因:

假设你打算发送 10MB 的数据,在你和你需要连接的客户端之间有一个中间层,并且它的传输速率非常低,例如 1MB/s。假设你可以辨别这个慢连接(或者更多的后续中间层)的速度,那么你有 2 个选择:

  1. 一次性将这 10MB 的数据发送完,然后看看会发生什么。
  2. 减慢速率使得你能够以 1MB/s 的速率传给它。

现在,无论你选择何种方式,你可能都会发生丢包的现象。所以这样看起来,你可能需要选择一次性发送所有的信息包这种方式,对吧?不!!实际上在你的数据流的中间环节丢包要比在你的数据流的最后丢包要好得多。假如在中间环节有些包被丢弃了,你需要送往的那个客户端可以察觉到这个事情,然后再告诉你,这样你就可以再次发送那些被丢弃的包,这样便没有多大的损失。但假如信息包在最末端被丢弃,那么客户端将完全没有办法知道你一次性发送了所有的信息包!所以基本上在某个时刻被丢弃的包没有让你收到 ACK 信号时,你需要启用超时机制,并且还得重新发送它们。而超时往往意味着需要花费很长时间!

所以为什么以更慢的速率发送数据会更好呢?假如你发送数据的速率快于连接中的瓶颈,这时所有的信息包将会在某个地方堆积成一个队列,这个队列将会被塞满,然后在你的数据流的最末端的信息包将会被丢弃。并且像我们刚才解释的那样,处于数据流最后面的信息包很有可能丢弃!所以相比于最初以合适的速率发送信息包,一次性发送它们将会触发超时机制,发送 10MB 的数据将会花费更长的时间。

我认为这非常酷,因为这个过程并不需要与互联网中的其他人合作 —— 即便其他的所有人都已非常快的速率传送他们的信息包,对你来说以合适的速率(中间层的瓶颈速率)传送你自己的信息包仍然更有优势。

如何辨别发送数据的合适速率:BBR!

在上面我说过:“假设你可以辨别出位于你的终端和服务器之间慢连接的速率……”,那么如何做到呢?来自 Google(Jacobson 工作的地方)的某些专家已经提出了一个算法来估计瓶颈的速率!它叫做 BBR,由于本次的分享已经很长了,所以这里不做具体介绍,但你可以参考 BBR:基于拥塞的拥塞控制来自晨读论文的总结 这两处链接。

(另外,https://blog.acolyer.org 的每日“晨读论文”总结基本上是我学习和理解计算机科学论文的唯一方式,它有可能是整个互联网上最好的博客之一!)

网络代码被设计为运行得“尽可能快“

所以,假设我们相信我们想以一个更慢的速率(例如以我们连接中的瓶颈速率)来传输数据。这很好,但网络软件并不是被设计为以一个可控速率来传输数据的!下面是我所理解的大多数网络软件怎么做的:

  1. 现在有一个队列的信息包来临;
  2. 然后软件读取队列并尽可能快地发送信息包;
  3. 就这样,没有了。

这个过程非常呆板 —— 假设我以一个非常快的速率发送信息包,而另一端的连接却非常慢。假如我所拥有的就是一个放置所有信息包的队列,当我实际要发送数据时,我并没有办法来控制这个发送过程,所以我便不能减慢这个队列传输的速率。

一个更好的方式:给每个信息包一个“最早的出发时间”

BBR 协议将会修改 Linux 内核中 skb 的数据结构(这个数据结构被用来表达网络信息包),使得它有一个时间戳,这个时间戳代表着这个信息包应该被发送出去的最早时间。

对于 Linux 网络栈我不知道更多的详情了,但对于我来说,这个协议最有趣的地方是这个改动并不是一个非常大的改动!它只是添加了一个额外的时间戳而已。

用时间轮盘替换队列!!!

一旦我们将时间戳打到这些信息包上,我们怎样在合适的时间将它们发送出去呢?使用时间轮盘

在前不久的“我们喜爱的论文”活动中(这是关于这次聚会描述的某些好的链接),有一个演讲谈论了关于时间轮盘的话题。时间轮盘是一类用来指导 Linux 的进程调度器决定何时运行进程的算法。

Van Jacobson 说道:时间轮盘实际上比队列调度工作得更好 —— 它们都提供常数时间的操作,但因为某些缓存机制,时间轮盘的常数要更小一些。我真的没有太明白这里他说的关于性能的解释。

他说道:关于时间轮盘的一个关键点是你可以很轻松地用时间轮盘实现一个队列(但反之不能!)—— 假如每次你增加一个新的信息包,在最开始你说我想让它现在就被发送走,很显然这样你就可以得到一个队列了。而这个时间轮盘方法是向后兼容的,它使得你可以更容易地实现某些更加复杂的对流量非常敏感的算法,例如让你针对不同的信息包以不同的速率去发送它们。

或许我们可以通过改善 Linux 来修复互联网!

对于任何影响到整个互联网规模的问题,最为棘手的问题是当你要做出改善时,例如改变互联网协议的实现,你需要面对各种不同的设备。你要面对 Linux 的机器、BSD 的机器、Windows 的机器、各种各样的手机、瞻博或者思科的路由器以及数量繁多的其他设备!

但是在网络环境中 Linux 处于某种有趣的位置上!

  • Android 手机运行着 Linux
  • 大多数的消费级 WiFi 路由器运行着 Linux
  • 无数的服务器运行着 Linux

所以在任何给定的网络连接中,实际上很有可能在两端都有一台 Linux 机器(例如一个 Linux 服务器、或者一个 Linux 路由器、一台 Android 设备)。

所以重点是假如你想大幅改善互联网上的拥塞状况,只需要改变 Linux 网络栈就会大所不同(或许 iOS 网络栈也是类似的)。这也就是为什么在本次的 Linux 网络会议上有这样的一个演讲!

互联网仍在改变!酷!

通常我以为 TCP/IP 仍然是上世纪 80 年代的东西,所以当从这些专家口中听说这些我们正在设计的网路协议仍然有许多严重的问题时,真的是非常有趣,并且听说现在有不同的方式来设计它们。

当然也确实是这样 —— 网络硬件以及和速度相关的任何设备,以及人们使用网络来干的各种事情(例如观看 Netflix 的节目)等等,一直都在随着时间发生着改变,所以正因为这样,我们需要为 2018 年的互联网而不是为 1988 年的互联网设计我们不同的算法。


via: https://jvns.ca/blog/2018/07/12/netdev-day-2--moving-away-from--as-fast-as-possible/

作者:Julia Evans 译者:FSSlc 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出