常见网路加速技术浅谈(二)

来自专栏软体定义网路20 人赞了文章

上一篇我们看了一些网路加速的技术,它们致力于减少处理每个网路数据包所需要的CPU时间,要么是把一些网路协议的运算卸载(offload)到网卡,要么是减少数据拷贝对CPU资源的消耗。在上一篇我们说过,网路加速要解决的核心问题是CPU可以用来处理每个网路包的时间变短了。因为带宽的增速比CPU处理速度的增速大,所以相同的CPU时间,需要处理的网路数据包更多了。如果换一个思路:减少网路数据包的数量,那相同的CPU时间,需要处理的网路数据包更少了,分给每个网路数据包的时间也更就多了。这次我们看看相关的一些技术。

Jumbo Frames

乙太网提出的时候是按照1500位元组MTU(Maximum Transmission Unit)设计的,也就是Ethernet Frame的payload(数据段)最大是1500位元组。为什么是1500位元组?这是一个效率和可靠性的折中选择。因为单个包越长,效率肯定越高,但相应的丢包概率也越大。反过来,单个包越小,效率更低,因为有效数据占整个网路数据比例更低,不过相应的丢包概率也更小。 因此,IEEE802.3规定了乙太网的MTU是1500。

网路传输的时候,MTU必须匹配,MTU1500向MTU9000的机器发数据没问题。但是反过来,MTU9000向MTU1500的机器发数据,因为数据太长,MTU1500的机器识别不了会丢包。因此,网路数据的收发端MTU必须匹配。另一方面,互联网从几十年前就开始构建,为了统一标准,增加兼容性,整个互联网都是根据IEEE802.3规定的MTU1500来构建。

但是,现在的网路设备可靠性有了很大的提升,可以稳定传输更大的网路包。Jumbo Frames就是MTU为9000位元组的Ethernet Frames。对于Jumbo Frames来说,每个网路数据包的有效数据占比更多,因为网路协议的头部长度是固定的,网路数据包变长了只能是有效数据更多了。另一方面,以10G网路为例,MTU1500需要CPU每秒处理超过800,000个网路包,而MTU9000只需要CPU每秒处理140,000个网路包。因此,在MTU9000下,单位时间CPU需要处理的网路包更少了,留给CPU处理每个网路包的时间更多了。

支持Jumbo Frames需要相应的硬体,最新的硬体基本都支持了,只需要简单的配置即可。但是Jumbo Frames在实际使用的时候有一定的局限性。因为Jumbo Frames提出时,互联网已经按照MTU1500搭建完了,而MTU又必须匹配,改造全网基本不太可能。所以Jumbo Frames一般只在数据中心内部网路使用,例如内部存储网路。连接互联网的MTU一般还是设置为1500。

GSO

GSO全称是Generic Segmentation Offload,它只在网路数据发送时有效。GSO的作者Herbert Xu说过「If we cant use a larger MTU, we can go for the next-best thing: pretend that were using a larger MTU.」有点像,现在我不能吃烧鸡,那老板来两片素鸡,比什么也没有强点。既然互联网是基于MTU 1500构建,在互联网上传输的网路包必须遵循MTU 1500,那如果在操作系统里面尽量晚进行IP Fragmentation,在TCP/IP协议栈里就会有一段「路径」,其上传递的网路数据是一个payload超过1500位元组的网路包,相当于在传递一个大MTU的网路数据。在这段「路径」上,CPU需要处理更少的网路数据包,相应的留给CPU处理每个网路包的时间就更多了。

其实上一篇介绍的TSO也有这个思想,从用户程序到网卡之间,一直都不进行TCP Segmentation和IP Fragmentation,数据包最大可以到64K。但是,TSO只支持TCP协议,并且需要硬体网卡的支持,而GSO就是为其他场合提出。其实严格来说,除了TCP,其他的网路数据大包变小包都是发生在IP层,因此属于IP Fragmentation,所以这里叫GS(egmentation)O并不是100%恰当。

因为不依赖硬体,又要尽可能晚的分段或者分片,所以GSO选择在发给网卡驱动的前一刻将大包分成多个小包。这样,虽然网卡收到的还是多个小的网路数据包,但是在TCP/IP协议栈里面,如下图所示,还是有一段路径,CPU需要处理少量的大包。

因为在发给网卡驱动的前一刻完成,所以GSO可以作为TSO的备份。在发给网卡驱动时检查网卡是否支持TSO,如果支持,将大包直接传给网卡驱动。如果不支持,再做GSO。

根据LinuxFoundation的文档,在MTU1500时,使用GSO可以使得网路吞吐量(throughput)提升17.5%。

LRO

LRO全称是Large Receive Offload,或者又称为RSC(Receive Side Coalescing),从名字可以看出它只在网路数据接收时有效。LRO是TSO的逆方向实现,是指网卡将同一个TCP连接的TCP Segments 合并成一个大的TCP包,再传给操作系统。这样避免了操作系统处理并合并多个小包,减少了CPU的运算时间,并且在TCP/IP协议栈,CPU需要处理更少的网路数据包。与TSO一样,LRO也需要网卡的支持。

但是与TSO不一样的是,LRO并没那么好用。因为TSO发生在数据的发送方,发送方掌握了网路数据的全部信息,发送方可以按照自己的判断控制发送的流程。而LRO发生在数据的接收方,而且是相对于数据发送方的非同步接收,所以LRO只能基于当前获取到的有限数据和信息做出合并,存在一定的困难。这就像我们拆一个东西很容易,但是要重新组装回去很难一样。

LRO可能会丢失重要的数据,例如数据发送方在Header加了一些栏位来区分不同的网路包。合并可能导致这些栏位的丢失,因为合并之后只有一个Header了。而且当操作系统需要转发数据时,合并之后的网路包可能需要重新被分段/片。再重新分成小包,原来Header里面的差异栏位就彻底丢失了。因为LRO的局限性,在一些最新的网卡上,LRO已经被删除了。

GRO

GRO全称是Generic Receive Offload,这是GSO在接收端的对应。GRO的作者与GSO是同一个人,都是Herbert Xu。不像GSO作为TSO的替补,GRO逐渐取代了LRO。因为GRO运行在系统内核,掌握的信息更多,GRO可以用更加严格的规则来合并网路数据包。因为合并的时候更严格,所以可以避免关键的信息丢失。另一方面,在一些需要转发的场合,GRO可以利用GSO的代码来重新分段。

其他的优点还有,GRO也更加通用,不仅不依赖硬体设备,还支持TCP协议以外的协议。

UFO

UFO全称是UDP fragmentation offload。从名字可以看出,这是针对UDP的优化。但是不像TCP,UDP没有Segmentation的过程,用户程序发给UDP多长的数据(当然要控制在64K以内),UDP都会转给IP层。IP层会根据MTU进行Fragmentation。UFO使得网路设备,例如网卡,可以将一个超长的UDP数据段(超过MTU),分成多个IPv4分片(fragment)。因为在网卡做了,所以,CPU的运算量被节省下来了。

不过,在最新的linux内核中,UFO已经被弃用了。因为除了TSO,其他的offload基本上都是在IP层做Fragmentation,那UFO也没有必要单独存在,因此它与GSO合并表示了。

tx-udp_tnl-segmentation

Overlay网路,例如VxLAN,现在应用的越来越多。Overlay网路可以使得用户不受物理网路的限制,进而创建,配置并管理所需要的虚拟网路连接。同时Overlay可以让多个租户共用一个物理网路,提高网路的利用率。Overlay网路有很多种,但是最具有代表性的是VxLAN。VxLAN是一个MAC in UDP的设计,具体格式如下所示。

从VxLAN的格式可以看出,以VxLAN为代表的Overlay网路在性能上存在两个问题。一个是Overhead的增加,VxLAN在原始的Ethernet Frame上再包了一层Ethernet+IP+UDP+VXLAN,这样每个Ethernet Frame比原来要多传输50个位元组。所以可以预见的是,Overlay网路的效率必然要低于Underlay网路。另一个问题比传50个位元组更为严重,那就是需要处理这额外的50个位元组。这50个位元组包括了4个Header,每个Header都涉及到拷贝,计算,都需要消耗CPU。而我们现在迫切的问题在于CPU可以用来处理每个网路数据包的时间更少了。

首先,VxLAN的这50个位元组是没法避免的。其次,那就只能降低它的影响。这里仍然可以采用Jumbo Frames的思想,因为50个位元组是固定的,那网路数据包越大,50位元组带来的影响就相对越小。

先来看一下虚拟机的网路连接图。虚拟机通过QEMU连接到位于宿主机的TAP设备,之后再通过虚机交换机转到VTEP(VxLAN Tunnel EndPoint),封装VxLAN格式,发给宿主机网卡。

理想情况就是,一大段VxLAN数据直接传给网卡,由网卡去完成剩下的分片,分段,并对分成的小的网路包分别封装VxLAN,计算校验和等工作。这样VxLAN对虚机网路带来影响就可以降到最低。实际中,这是可能的,但是需要一系列的前提条件。

首先,虚拟机要把大的网路包发到宿主机。因为虚拟机里面也运行了一个操作系统,也有自己的TCP/IP协议栈,所以虚拟机完全有能力自己就把大的网路包分成多个小的网路包。从前面介绍的内容看,只有TSO才能真正将一个大的网路包发到网卡。GSO在发到网卡的时候,已经在进入驱动的前一刻将大的网路包分成了若干个小的网路数据包。所以这里要求:虚机的网卡支持TSO(Virtio默认支持),并且打开TSO(默认打开),同时虚机发出的是TCP数据。

之后,经过QEMU,虚拟交换机的转发,VTEP的封装,这个大的TCP数据被封装成了VxLAN格式。50个位元组的VxLAN数据被加到了这个大的TCP数据上。接下来问题来了,这本来是个TCP数据,但是因为做了VxLAN的封装,现在看起来像是个UDP的数据。如果操作系统不做任何处理,按照前面的介绍,那就应该走GSO做IP Fragmentation,并在发送给网卡的前一刻分成多个小包。这样,如果网卡本来支持TSO现在就用不上了。并且更加严重的是,现在还没做TCP Segmentation。我们在上一篇花了很大的篇幅介绍其必要性的TCP Segmentation在这里也丢失了。

对于现代的网卡,除了TSO,GSO等offload选项外,还多了一个选项tx-udp_tnl-segmentation。如果这个选项打开,操作系统自己会识别封装成VxLAN的UDP数据是一个tunnel数据,并且操作系统会直接把这一大段VxLAN数据丢给网卡去处理。在网卡里面,网卡会针对内层的TCP数据,完成TCP Segmentation。之后再为每个TCP Segment加上VxLAN封装(50位元组),如下图右所示。这样,VxLAN封装对于虚拟机网路来说,影响降到了最低。

从前面描述看,要达成上述的效果,需要宿主机网卡同时支持TSO和tx-udp_tnl-segmentation。如果这两者任意一个不支持或者都不支持。那么系统内核会调用GSO,将封装成VxLAN格式的大段TCP数据,在发给网卡驱动前完成TCP Segmentation,并且为每个TCP Segment加上VxLAN封装。如下图左所示。

如果关闭虚拟机内的TSO,或者虚拟机内发送的是UDP数据。那么在虚拟机的TCP/IP协议栈会调用GSO,发给虚拟机网卡驱动的前一刻,完成了分段、分片。虚拟机最终发到QEMU的网路数据包就是多个小的网路数据包。这个时候,无论宿主机怎么配置,都需要处理多个小的网路包,并对他们做VxLAN封装。

总结

零零碎碎说了这么多,这些网路加速技术一般不需要使用者去配置。因为如果支持的话,默认都是打开的。使用的时候大家只需要确认自己的系统是否带有这些功能。又或者,在调试一些问题的时候,看看是否是因为这些功能引起的,如果是的话,手动关闭它们。

参考链接:

ibm.com/developerworks/

slideshare.net/eurobsdc

kernel.org/doc/Document

kernel.org/doc/Document

wiki.linuxfoundation.org

lwn.net/Articles/358910

hustcat.github.io/udp-a

推荐阅读:

相关文章