让网络更快一点儿

翻译:马少兵、林业
原文:LPC: Making the net go faster
By Jonathan Corbet

September 13, 2011

几乎所有google的服务都是通过 Internet 交付给用户使用,因此该公司特别有动力改进网络的运行模式。在2011召开的Linux Plumbers Conference featured presentations的网络会议上,三个google的工程师各提出了一个对网络有显著改变的建议。从他们三个人的建议可以看出,networking 还有很大的改进空间。

Proportional rate reduction

这个“拥塞窗口”主要取决于TCP发送者的想法:在中间环节超载的情况下,多少的数据能够被传送至末端。丢弃包常常意味着拥塞窗口过大,因此需要TCP正常实现过程中减少窗口当丢失发生时。但是减少拥塞窗口,就会减低性能;如果丢包仅是一次性事件,减少窗口大小是完全没必要的。RFC3517描述了一种算法,以使一次丢包后,加快连接速度。但是Nandita Dukkipati说,我们可以做的更好。

根据Nandita的说法,Google 的网络会话出问题的大部分原因集中在一个点上,它将导致花费7-10倍的时间才能完成会话。RFC3517是这个问题的一部分。这个算法当一次包丢失后,会立即减少一半的拥塞窗口,意味着发送者必须等待ACKs一半在传输途中的包(如果丢失包后拥塞窗口还是满的)。这就导致发送者将保持更长的等待时间。在简单的情况下(单个包丢失在长时间的传输过程中),这种做法是足够好的。但是在处理短数据流或很大比例的包丢失时,将阻碍工作。

现在的Linux系统没有使用严格的RFC3517。它被一个改进的算法“率减半”所代替。拥塞窗口不会立即减半。一旦 TCP 开始尝试恢复丢失包,每个ACK(主要是告诉末端两个包的接收者)将引起拥塞窗口大小减少。在传输过程中的全套发生上述过程,窗口大小将减半,但是发送者将持续发送(低速率)。这种方案最终的结果是减缓数据流和降低等待时间。

但是率减半还有提升空间。ACKs依赖于自身;延长的损失将显著引起拥塞窗口数量的减半和恢复的缓慢。这个算法也没有处理拥塞窗口的速率达到最高的可用值的过程,直到整个恢复过程完成后,速率才到达最高值。因此这将花费更长的时间来返回到最高速率。

成比例速率减少算法采用了一种不用的方法。第一步就是评估和计算传输的数据量,然后根据拥塞控制算法,计算拥塞窗口数量是多少。如果在管道中的数据量小于目标窗口的值,系统将直接进入TCP慢启动算法来备份窗口。然而当连接中发送大量丢失时,边开始重建窗口,而不是长时间匍匐一个小窗口。

相反如果传输的数据量至少达到了一个新的拥塞窗口,一个相似的率减半算法将被使用。相对新的拥塞窗口计算实际的减少量,而不是严格的减半。对于大和小的丢失,注重于使用评估的传输数据量,而不是请求的数据量。这使得恢复更加顺利和避免不必要的窗口数量的减少。

那多少是最合适的呢? Nandita认为,根据google在一些系统上已经运行的实验结果显示,平均等待实际减少3-10%。恢复实际减少5%。这个算法已经广泛应用于google服务器上;该算法也被3.2开发周期所合并。更多的信息请查看这份 draft RFC

TCP fast open

打开一个TCP连接需要一个three-packet 握手:客户端发送一个SYN(连接请求)数据包,服务端返回一个SYN-ACK(确认)响应,以及最后由客户端发出的一个ACK(确认)。直到握手完成,连接才可以传送数据,所以握手机制在每个连接都有一个不可避免的开始延迟。但是,问问Yuchung Cheng,如果想要在握手数据包中携带数据信息将会发生什么。对于一些简单的事务,例如一个接下来要发送一个页面内容的HTTP GET请求,伴随着握手数据包发送相关联的数据将会消除延迟。这个想法的结果就是TCP fast open的设想。

RFC 793(关于TCP的)确实允许数据包含在握手的数据包中发送,数据被限制为在握手完成之后再被 tcp 栈传递给应用程序。这样就可以通过避免最后一次请求来加速一次TCP连接发送数据的进程,但是还有一些障碍需要解决掉。一个很明显的问题就是会加大SYN泛滥攻击的几率,即使它们只影响内核后果也足够糟糕;如果每个接收到的SYN数据包都要占用应用程序的内核资源,那么拒绝服务的可能性就会变得很大。

Yuchung叙述了一种快速打开的方法试图解决大部分的问题。第一步是每个服务器创建一个 per-server secret,然后根据每个客户端的信息 hash,给每个客户端生产 per-client cookie。当客户首次发起 TCP SYN 请求时,这个cookie被作为一个special option包含在 SYN-ACK数据包中发送到客户端;客户端可以保存它,然后在未来的fast open时利用它。首先得到cookie的需求是一种低端的防止SYN泛滥攻击的方法,但是它确实让攻击变得有些困难了。另外,server端的secret可以相对频繁的更改,并且,如果当server发现有很多的连接时,fast open将会被禁用,直到连接数目回归正常。

一个仍然存在的问题是,互联网上大概有5%的线上系统将会丢弃那些包含了未知option数据的SYN数据包。这种情况下TCP fast open就无法使用了。客户端必须记住那些fast-open SYN数据包没有发送成功的cases,以后再遇到这些cases就使用普通打开。

缺省情况下fast open不会发生;连接双方的应用程序都必须特地的要求使用它。在客户端,sendto()系统调用就是用来请求一个fast-open连接;伴随着新的MSG_FAST_OPEN标志, 它的功能就像是connect()与sendmsg()的结合。在服务端,伴随着TCP_FAST_OPEN option 的setsockopt()调用将会允许fast opens。不管哪种方式,应用程序都不用担心去处理类似fast-open cookies这样的问题。

在google的测试中,TCP fast open已经被证实可以减少4%到40%的页面载入时间。自然的,在连接往返时间开销很大的情况下这项技术效果最好;延迟越高,移除它的价值就越大。很快,一个实现了这个特性的patch将会被提交。

Briefly: user-space network queues

之前的两个讨论都是在关注提高数据在网络上传输的效率问题。Willem de Bruijn则是关注与在本地主机上的网络进程。特别是,他是在使用高端的硬件工作:高速的网络连接,数目众多的处理机,并且,更重要的是,它拥有可识别特定流然后直接把包传入特定连接队列的智能网络适配器。当kernel感知到这个数据包的时候,它早已被接收并放置在合适的位置等着应用程序来请求数据了。

实际的对数据包的处理工作将会在应用程序的上下文环境中执行。所以"合适的位置"这里还包括了正确的上下文和正确的CPU。在软件IRQ级的中间处理就省略掉了。Willem甚至描述了一个新的接口,靠它应用程序可以通过一段共享内存段直接从kernel接收数据包。

换句话说,这段讨论描述了几种网络通道的概念,通过它们数据包被尽可能近的推送到应用程序。还有大量的细节问题需要涉及,包括为了类似防火墙等原因导致的通道挂起。利用 sysfs 向用户空间发送数据包的建议很难通过评审。但是这项工作最终可能会达到一个普遍有用的地步;有兴趣的人可以在 the unetq page 处找到patches

Topic: sohulinux