深入理解 Open vSwitch(一):Open vSwitch 设计与实现

前言

最近笔者在学习 Open vSwitch 时,把所有精力都放在分析其源代码上,结果深陷细节无法自拔。于是通过阅读 OVS 开发团队在 NSDI 2015 会议上发表的论文 —— 《The Design and Implementation of Open vSwitch》,来理解 Open vSwitch 的设计与实现,当然对于论文中的一些内容目前还是无法理解。

为了方便自己和他人在学习时查阅,笔者将这篇论文翻译为中文。由于笔者水平有限,如有纰漏请指正。

0、摘要

我们描述了 Open vSwitch 的设计与实现,这是一个为所有主流虚拟机管理平台而生的多层开源虚拟交换机。Open vSwitch 是为虚拟环境中的网络从头设计的,这导致了在主要设计上与传统软件交换机有很大差异。我们详细介绍了 Open vSwitch 用于优化其操作和节省交换机监控程序资源的高级流分类技术和流缓存技术。我们根据在过去 7 年里使用和改进 Open vSwitch 的经验,评估了 Open vSwitch 的性能。

1、介绍

在过去的 15 年里,虚拟化改变了我们的计算方式;例如,许多数据中心已经完全虚拟化,以提供快速资源调配,向云端扩展,以及在灾难恢复期间提供可用性。尽管虚拟化依旧能承担所有类型的工作负载,然而虚拟机的数量已经超过了服务器的数量,并且进一步的虚拟化没有停止的迹象。

服务器的虚拟化给数据中心网络带来了根本性的转变。新的网络接入层已经出现,在这个接入层中,大部分网络端口都是虚拟化的、而非物理的,因此,越来越多用在工作负载的第一跳交换机,被实现在虚拟机监控程序中。在早期,这些虚拟机监控程序中的虚拟交换机主要负责提供基本的网络连接。实际上,它们只是简单地将物理二层网络扩展到虚拟机上。随着虚拟化工作负载的数量激增,这种实现方式的局限性也变得明显:为新的工作负载重新配置和准备物理网络,会降低资源调配的速度,同时工作负载与物理二层网络的耦合严重限制了它们相对于底层网络的移动性和可扩展性。

这些压力导致了网络虚拟化的出现。在网络虚拟化中,虚拟交换机成为了虚拟机网络服务的主要提供者,通过在虚拟机监控程序之间传输 IP 隧道报文的方式,与数据中心的物理网络隔离开。这种实现方式使得虚拟交换机与其底层物理网络解耦,并且通过利用通用处理器的灵活性,为虚拟机、它们的租户和管理员提供与物理网络相同的逻辑网络抽象、服务和工具。

网络虚拟化需要一款功能强大的虚拟交换机 —— 转发功能必须以每个虚拟端口为基础进行连接,目的是与管理员配置的逻辑网络抽象相匹配。同时这些抽象是跨虚拟机监控程序而实现的,这样可以进行细粒度的集中协调。这种实现方式与早期的静态的、使用硬编码转发通道的虚拟交换机完全不同,完全足以为虚拟机提供与物理网络的二层连接。

正是在这种背景下:日益复杂的虚拟网络,网络虚拟化的出现,以及现有虚拟交换机的局限性,使得 Open vSwitch 迅速流行起来。今天,在 Linux 平台上,Open vSwitch 和大多数虚拟机监控程序以及容器系统一起工作,包括 Xen、KVM 和 Docker。Open vSwitch 也可以在 FreeBSD 和 NetBSD 操作系统上开箱即用,同时在 VMware ESXi 和 Microsoft Hyper-V 虚拟机监控程序的移植工作也在进行中。

在这篇论文中,我们描述了 Open vSwitch 的设计与实现。其设计的关键要素围绕着 Open vSwitch 所部署的生产环境所要求的性能,以及网络虚拟化所要求的可编程性。传统的网络设备都是通过专业的软件或硬件来实现高性能,相比之下,Open vSwitch 旨在实现灵活性和通用性。其必须在不借助专业化的情况下实现高性能,以适应所支持平台的差异,同时与虚拟机监控程序及其工作负载共享资源。因此,这篇论文首先需要关注这个问题 —— Open vSwitch 如何在不牺牲通用性的情况下获得高性能。

论文的其余部分组织如下:第 2 节提供了关于虚拟化环境的更多背景信息;第 3 节描述了 Open vSwitch 的基本设计;第 4、5、6 节描述了 Open vSwitch 是如何通过流缓存来优化虚拟化环境的要求的,缓存是如何对包括报文分类器在内的整个设计产生广泛影响的,以及 Open vSwitch 是如何管理流缓存的;第 7 节通过对分类和缓存进行微型基准测试来评估 Open vSwitch 的性能,以及评估了 Open vSwitch 在多租户数据中心里的性能。最后,我们在第 8 节讨论了当前正在进行的、未来的、以及相关的工作。

2、设计约束和基本原理

虚拟交换机的运行环境与传统的网络设备的环境截然不同。下文我们简要讨论这些差异所带来的限制和挑战,以揭示 Open vSwitch 设计选择的基本原理,并强调其独特之处。

资源共享。 传统的网络设备倾向于使用专用的硬件资源,以在最坏情况下实现线速性能的设计。虚拟交换机与之不同,资源节约才是至关重要的。虚拟交换机能否在最坏情况下达到线速是次要的,最重要的是最大化利用虚拟机监控程序的资源。也就是说,和物理环境相比,虚拟化环境中的网络聚焦于一般情况的优化,而非最坏情况。这并不是说最坏情况不重要,因为它们确实会在实践中发生。端口扫描、P2P 服务器以及网络监控,都会产生异常的流量,但必须对这些情况进行支持。这一原则导致我们大量使用流缓存和其他形式的缓存,这些工作在一般情况下(高缓存命中率)可以减少 CPU 使用并提高转发速率。

放置。 在网络边缘放置虚拟交换机,既简单又复杂:在网络拓扑中位于叶子节点的问题,以及与虚拟机监控程序和虚拟机共存,可以消除许多标准的网络问题;然而,虚拟交换机的放置也会使扩展变得更复杂。单个虚拟交换机通过点对点 IP 隧道与几千个虚拟交换机相连接的情况并不少见。当虚拟机启动、迁移和关机时,虚拟交换机会接收到转发状态的更新信息;虽然与虚拟交换机直接相连的物理网络接口相对较少,但是远程的虚拟机监控程序的变更可能也会影响虚拟交换机的状态。特别是在数千台(或更多)虚拟机监控程序的大型部署环境中,转发状态可能处于不断变化的状态。本文讨论的受这一原则影响的设计的主要例子是 Open vSwitch 分类算法,该算法专为 O(1) 更新而设计。

SDN、使用场景与生态系统。 Open vSwitch 有三点额外的特别要求,这些要求导致其设计区别于其他虚拟交换机:

首先,Open vSwitch 从一开始就是一个 OpenFlow 交换机。它故意不与单一用途、紧密集成的网络控制栈绑定在一起,而是通过 OpenFlow 实现可重新编程。这与其他虚拟交换机的 datapath 模型不同,其他虚拟交换机与转发硬件 ASIC 类似,它们的包处理流水线是固定的,只能配置预先安排的功能。(Hyper-V 虚拟交换机可以通过添加二进制模块的方式进行功能扩展,但通常每个模块只能向 datapath 添加另一个单一用途的功能)。

在早期,OpenFlow 的灵活性在 SDN 中是至关重要的,但很快就暴露出问题:像网络虚拟化这种高级使用场景会导致较长的报文处理流水线,从而导致分类的负载比传统虚拟交换机要更高。为了避免 Open vSwitch 比其他虚拟交换机消耗更多的虚拟机监控程序的资源,其被迫实现了流缓存。

第三,与其他主流虚拟交换机不同,Open vSwitch 支持开源和多平台。与那些只能在单一环境上运行的闭源虚拟交换机不同,Open vSwitch 的运行环境通常是由操作系统和虚拟机监控程序的用户来选择,这迫使其设计成模块化和可移植。

3、设计

3.1、概述

在 Open vSwitch 中,有两个主要组件负责报文转发。第一个、也是更大的组件是 ovs-vswitchd,这是一个用户空间守护进程,并且在不同的操作系统中基本相同。另一个主要组件是 datapath 内核模块,这一组件会为不同操作系统进行专门的编写,目的是提升性能。

image

图 1:Open vSwitch 的组件与接口。流的第一个报文会导致缓存未命中,内核模块则将报文传送到用户空间组件,然后该组件会将后续报文的转发决策缓存到内核模块中。

图 1 描述了 OVS 的两大组件是如何协同工作以转发报文的。内核中的 datapath 模块首先会从网卡或者虚拟机的虚拟网卡中接收报文。此时,ovs-vswitchd 可能已经告知了 datapath 如何处理该类型的报文,也可能还没有。在前一种情况下,datapath 会简单地遵循 ovs-vswitchd 下发的、被称为 actions 的指令,这些 actions 列出了应该将报文发往哪些物理端口或隧道。actions 还可以指定对报文进行修改、采样或丢弃的指令。在另一种情况下,datapath 还没有被告知应该如何处理报文,那么 datapath 则会将报文传送到 ovs-vswitchd。在用户空间,ovs-vswitchd 会决定如何处理此报文,然后将报文以及报文的处理方法返回给 datapath。通常情况下,ovs-vswitchd 还会告诉 datapath 去缓存这些 actions,以便在后续处理类似的报文。

在 Open vSwitch 中,随着时间的推移,流缓存发生了很大变化。最初的 datapath 是 microflow cache,本质上是缓存了每个传输连接的转发决策。在后来的版本中,datapath 有了两层缓存:microflow cache 和被称为 megaflow cache 的辅助层,后者缓存了单个连接之外的流量聚合的转发决策。我们将会在第 4 节更详细地讨论缓存主题。

Open vSwitch 通常被用作 SDN 交换机,并且控制转发的主要方式是 OpenFlow。通过一个简单的二进制协议,OpenFlow 允许控制器对流表以及流进行添加、删除、更新、监控和收集统计信息,也允许控制器与交换机之间传输报文。在 Open vSwitch 中,ovs-vswitchd 从 SDN 控制器中接收 OpenFlow 流表,并且使用 OpenFlow 流表来匹配从 datapath 模块那接收到的报文,收集需要应用的相应 actions,然后将结果缓存到内核 datapath 中。也就是说,datapath 模块并不知道 OpenFlow 协议的细节,从而进一步地简化了 datapath。从 OpenFlow 控制器的角度来看,缓存,以及用户空间和内核空间组件的分离,是不可见的实现细节:在控制器看来,每一个报文都会经历一系列 OpenFlow 流表,并且交换机会从与报文相匹配的流中找到优先级最高的一个,然后执行相关的 OpenFlow actions。

Open vSwitch 的流编程模型在很大程度上决定了其所能支持的使用场景,为此,Open vSwitch 针对标准 OpenFlow 做了很多扩展,以适应网络虚拟化。我们将会简单讨论这些扩展,但在此之前,我们将重点放在设计的性能关键方面:报文分类和内核-用户空间接口。

3.2、报文分类

在通用处理器上进行算法报文处理是非常昂贵的。并且由于匹配形式的广泛性,可能会对以太网地址、IPv4 和 IPv6、TCP 和 UDP 端口,以及其他字段的任意组合进行测试,甚至包括像交换机入口端口的报文元数据,因此在 OpenFlow 中进行报文分类是非常昂贵的。

Open vSwitch 对其所有报文分类都使用了 tuple space search(元组空间搜索) 分类器,包括在内核进行分类和在用户空间进行分类。为了理解元组空间搜索是如何工作的,假设 Open vSwitch 流表中所有的流以相同的方式匹配相同的字段,例如,所有的流都匹配源和目标以太网地址,仅仅这两个字段。元组空间搜索分类器会将流表实现为单个哈希表。如果后续控制器添加了不同匹配形式的新流,分类器则会创建第二个哈希表,对这些流中匹配的字段进行哈希。(元组空间搜索分类器中哈希表的元组,恰恰是构成这些哈希表的 key 的字段集合,但我们经常将哈希表本身简称为元组。)由于现有两个哈希表,那么必须在这两个哈希表中查找。如果查找不到,则说明流表中不存在匹配的流;如果在其中一个流表查找到匹配项,那么查找结果就是所对应的流;如果在两个流表中都查找到匹配项,那么查找结果就是优先级更高的流。随着控制器继续添加不同匹配形式的新流,分类器同样会为每一个唯一匹配项添加哈希表,并且分类器的搜索必须查找每一个哈希表。

尽管元组空间搜索的查找复杂度远不如现有技术水平,但是在实践中我们发现其流表性能表现良好,并且与决策树分类算法相比,它有三个具有吸引力的地方。第一,它支持高效的、常数时间内的更新(更新转换为单个哈希表操作),这使它适合在集中式控制器经常增删流的虚拟化环境下使用,在这种环境下,有时候为了响应整个数据中心的更改操作,每个虚拟机监控程序每秒需要执行多次操作。第二,元组空间搜索可以扩大到任意数量的报文头部字段,而不需要任何算法修改。最后,元组空间搜索使用的内存是随着流的数量线性增长。

复杂的 SDN 控制器会使用大量流表,进一步增加了报文分类的相对成本。例如,VMware 网络虚拟化控制器的流表,在报文处理流水线中至少使用 15 个表来查找每个报文。较长的流水线由两个因素产生:交叉乘积减少阶段通常会显著增加流表大小,以及开发者倾向于模块化设计流水线(译注:reducing stages through crossproducting would often significantly increase the flow table sizes and developer preference to modularize the pipeline design)。因此,与单个分类器的查找性能相比,更重要的是减少单个报文所需的流表查找数量。

3.3、将 OpenFlow 作为编程模型

最初,Open vSwitch 专注于实现一种反应式(reactive)的流编程模型:对流量作出响应的控制器负责安装匹配每个支持 OpenFlow 的字段的 microflow。这种实现方式很容易支持软件交换机和控制器,并且早期的研究表明这种实现方式是足够的。然而,反应式 microflow 编程模型很快证明在小型部署环境之外使用是不切实际的,Open vSwitch 不得不改用主动式(proactive)的流编程模型以限制其性能成本。

在 OpenFlow 1.0 中,一条 microflow 大概有 275 个比特用于存储信息,因此一个 microflow 流表可以拥有 2 的 275 次方或更多的条目(译注:记住,microflow 缓存了每个传输连接的转发决策)。因此,主动式的流表需要支持通配符匹配来覆盖所有可能的报文的头部空间。如果只有单个表,这会导致一个“向量积”(cross-product)问题:假设根据字段 A 的 n1 个值和字段 B 的 n2 个值来改变报文的处理方式,那么在一般情况下,必须安装 n1 x n2 个流,即使基于 A 和 B 所采取的 actions 是相互独立的。Open vSwitch 很快就引入了一个被称为 resubmit 的扩展动作,这一动作允许报文查询多个流表(或者多次查询一个表),从而汇总为最终的操作。这一改进解决了“向量积”(cross-product)问题,因为一个流表可以包含 n1 个查询字段 A 的流,而另一个流表可以包含 n2 个查询字段 B 的流。resubmit action 还支持基于一个或多个字段值的多路分支的编程模式。后来,专注于硬件的 OpenFlow 供应商找到了一种实现方法:通过转发硬件 ASIC 更好地连续查询多个表;并且 OpenFlow 1.1 开始支持多表查询。Open vSwitch 采用了这种新模型,但为了向后兼容,同时还保留了 resubmit action,因为新模型不允许递归,只允许通过固定的流表流水线来转发报文。

此时,控制器已经可以在 Open vSwitch 流表中进行编程,这些流表根据任意逻辑链路的报文头部来作出决策,但是控制器还是无法访问临时存储。为了解决这个问题,Open vSwitch 以另外一种方式扩展了 OpenFlow:通过新增一个被称为“寄存器”(registers)的元数据字段,而流表可以匹配这些字段,以及修改或复制这些字段的附加 actoins。例如,通过这种方式,流可以在流水线的早期就确定了物理目的地,然后通过相同的处理步骤对报文进行处理,无论选择的目的地是什么,都会使用指定的目的地将报文发送出去。另一个例子,VMware NVP 网络虚拟化控制器使用寄存器通过逻辑 L2 和 L3 拓扑来跟踪报文的处理流程,该拓扑是作为覆盖在物理 OpenFlow 流水线上实现的逻辑 datapath。

OpenFlow 专门用于对交换机进行基于流的控制。它不能创建或销毁 OpenFlow 交换机,不能添加或删除端口,不能配置 QoS 队列,不能将 OpenFlow 控制器和交换机关联起来,不能启用或禁用 STP(生成树协议),等等。在 OpenFlow 中,这些功能通过单独的组件进行控制 —— 配置数据库。如图 1 所示,SDN 控制器可以通过 OVSDB 协议来连接 ovsdb-server,进而访问配置数据库。一般来说,在 Open vSwitch 中,OpenFlow 控制的是可能快速变化和短暂的数据,而配置数据库存储着更持久的状态数据。

4、流缓存设计

本节介绍了 Open vSwitch 中流缓存的设计,以及它是如何发展到当前状态的。

4.1、microflow 缓存

在 2007 年,当时刚开始在 Linux 平台上开发 Open vSwitch,只有在内核中的报文转发在实践中具有良好的性能,因此最初的实现是将所有的 OpenFlow 处理流程都放在内核模块中。内核模块从网卡或虚拟机中接收到数据报文,通过 OpenFlow 流表进行分类(使用标准的 OpenFlow 匹配项和 actions),并在需要时对报文进行修改,最后将其发送到另一个端口。因为在内核中开发以及分发和更新内核模块的相对难度,这种实现方式很快就变得不切实际。很明显,在内核中的 OpenFlow 实现是不能作为上游 Linux 贡献的,这是对采用内核组件的软件的一个重要要求。

我们的解决方法是将内核模块重新实现为 microflow 缓存,在这种缓存中,单个缓存项与 OpenFlow 所支持的所有报文头部字段完全匹配。这通过将内核模块实现为简单的哈希表而非复杂的通用报文分类器,以支持任意字段和掩码,从而实现了根本性的简化。在这一设计中,缓存条目是相当细粒度的,并且可以与单个传输连接中的大部分报文相匹配:即使对于单个传输连接,网络路径以及相应的 IP TTL 字段的变化也会导致缓存未命中,然后将报文传送到用户空间,用户空间会参考实际的 OpenFlow 流表来决定如何转发该报文。这意味着关键性能维度是流的设置时间,即内核向用户空间汇报 microflow 缓存未命中以及用户空间回复的时间。

在多个 Open vSwitch 的版本中,我们采取了多种技术来减少 microflow 流缓存的设置时间。例如,通过批量进行流设置来减少设置一个 microflow 所需的平均系统调用次数,可以将流设置性能提高 24%。最后,我们还将流设置负载分散到多个用户空间线程,以从多个 CPU 核中受益。我们从 CuckooSwitch 那里获得灵感,采用了乐观 cuckoo hash(布谷鸟哈希)和 RCU 技术来实现非阻塞的多读单写的流表。

在对这类用户反馈的问题进行一般性优化之后,我们将注意力集中在延迟敏感的应用程序的性能上,这要求我们重新考虑简单的缓存设计。

4.2、megaflow 缓存

虽然 microflow 缓存在大多数流量模式下运行良好,但在大量的短连接的情况下,其性能会严重下降。在这种情况下,许多数据报文会缓存未命中,这不仅会导致用户空间和内核空间之间的切换,而且要执行一长串昂贵的报文分类。虽然批量处理和多线程可以在一定程度上减缓这种压力,但这些并不足以完全支持这种工作负载。

我们用 megaflow 缓存替代了 microflow 缓存。megaflow 缓存是支持通用匹配的、单独一个的流查找表,例如,它支持缓存比单个连接更大的流量的转发决策。由于 megaflow 缓存支持匹配任意报文字段,所以它看上去比 microflow 缓存更像一个普通的 OpenFlow 表,但它在运行时仍然非常简单和轻巧,原因有二:第一,它没有优先级,这加速了报文分类,内核中的元组空间搜索可以在找到任何匹配时立即终止,而不是继续查找更高优先级的匹配,直到查找完所有特定掩码的哈希表(为了避免歧义,用户空间只安装不相交的 megaflow,这些 megaflow 之间不会有重叠);第二,只有一个 megaflow 分类器,而不是它们的流水线,因此用户空间将所有相应的 OpenFlow 表的行为整合在一起后,再将 megaflow 表项安装到内核中。

megaflow 的查找成本接近于通用报文分类器,即使它不支持流的优先级。查找 megaflow 分类器需要查找其每一个哈希表,直到找到匹配项;正如 3.2 节所讨论的那样,流表中每一种唯一的匹配项都会在分类器中生成一个哈希表。假设每个哈希表包含一个匹配项的可能性相同,那么匹配的报文平均需要搜索 (n + 1) / 2 个表,而不匹配的报文需要搜索所有 n 个表。因此,对于 n > 1(这是最常见的情况),基于分类器的 megaflow 搜索需要比搜索基于 microflow 查找更多的哈希表。因此,megaflow 自身就产生了一种权衡:megaflow 相比于 microflow,可以减少报文进入用户空间的次数,但是增加了报文在内核空间查找哈希表的次数。

Open vSwitch 通过将 microflow 缓存保留为一级缓存来解决 megaflow 的成本问题(在 megaflow 缓存之前先查询 microflow 缓存)。microflow 缓存是一个哈希表,将 microflow 映射到其所匹配的 megaflow。因此,在 microflow 的第一个报文经过内核中的 megaflow 表,并查询了内核中的分类器之后,会在 microflow 缓存中创建一条精确匹配的缓存项,这条精确匹配的缓存项允许 microflow 中相同的后续报文可以快速定向到合适的 megaflow。这将 megaflow 的成本从每个报文降低到每个 microflow。精确匹配的缓存是真正意义上的缓存,因为它的活动对用户空间不可见(但是它会对性能有影响)。

一个 megaflow 流表表示所有在用户空间的 OpenFlow 流表的交叉乘积(cross-product)的活动子集。为了避免主动式(proactive)交叉乘积计算的成本,并仅使用与当前转发流量相关的条目来填充 megaflow 缓存,Open vSwitch 的用户空间守护进程以增量和反应式(reactively)的方式计算缓存项。当 Open vSwitch 通过用户空间流表来处理报文,并在每个表中分类报文时,它会跟踪报文字段中的某些位域,这些位域作为分类算法的一部分被查询。所生成的 megaflow 必须匹配任意字段(或者字段的一部分),这些字段作为决策的一部分来使用。例如,如果分类器将 OpenFlow 表中的目标 IP 字段视为其流水线的一部分,那么 megaflow 缓存项的条件也必须与目标 IP 相匹配。这意味着 megaflow 缓存的填充是由传入的报文驱动的,随着流量的逐渐汇聚,新的缓存项被填充而旧的缓存项被删除。

上述的讨论掩盖了一些细节。基本算法虽然正确,但是会产生比所需更具体的匹配条件,这会导致缓存命中率不理想。下文的第 5 节描述了 Open vSwitch 如何修改元组空间搜索来生成更好的 megaflow 进行缓存。之后,第 6 节讲述了缓存失效。

5、缓存感知报文分类

现在,我们将重点放在对基本元组搜索算法(在第 3.2 节中总结)所做的细化完善上,以提高其对流缓存的适用性。

5.1、问题

当 Open vSwitch 在用户空间通过其 OpenFlow 表处理报文时,它会跟踪报文字段中的位域,这些位域作为转发决策的一部分被查询。这种对报文头部字段的位域跟踪,在使用简单的 OpenFlow 流表构建 megaflow 时是非常有效的。例如,如果 OpenFlow 表只查看以太网地址(基于 L2 MAC 学习的流表也是如此),那么其所生成的 megaflow 也只会查看以太网地址。例如,端口扫描(不改变以太网地址)不会导致报文被传送到用户空间,因为它们的 L3 和 L4 头部字段将会被通配符匹配,从而产生接近理想的 magaflow 缓存命中率。然而,如果表中的一个流需要匹配 TCP 目标端口,元组空间搜索则会考虑搜索每个数据报文的 TCP 目标端口。然后每一个 megaflow 也将会匹配 TCP 目标端口,并且端口扫描的性能会下降。

我们不知道是否存在有效的算法来生成最优的、最抽象的 megaflow,因此在开发过程中,我们将注意力集中在生成越来越好的近似值上。未能匹配到所包含的字段可能会导致不正确的报文转发,而这种错误是不可接受的,所以我们的近似值偏向于在更多字段上进行匹配。以下各节描述了我们已经集成到 Open vSwitch 中的这类改进。

5.2、元组优先级排序

在元组空间搜索分类器中查找通常需要搜索每个元组。即使可能对某个元组的搜索已经找到了匹配项,但是搜索仍必须查找其他元组,因为其他元组可能包含具有更高优先级的匹配流。

我们对此的改进方式是,对于每个元组 T ,跟踪在 T 中的所有流中的最大优先级 T.pri_max。我们还修改了查找代码,将元组按优先级从高到低进行查找,这样的话,当需要匹配优先级为 F.pri 的流 F 时,一旦查找到某个元组的最大优先级小于或等于 F.pri,则立即停止查找,因为在后续的元组中无法找到更好的匹配了。图 2 展示了算法细节。

image

图 2:使用优先级排序的元组空间搜索,用于匹配目标报文头部 H。

举例说明,我们检查了由生产环境下的 VMware NVP 控制器所安装的 OpenFlow 表。该表包含了 29 个元组。在这 29 个元组中,有 26 个元组包含单个相同优先级的流。当按优先级降序进行查找时,总是在与这 26 个元组之一匹配成功后立即停止。在其他的元组中(这 26 个元组之外),有两个元组包含了具有两个不同优先级的流,这些优先级高于后续的元组,因此在这两个元组中的搜索也是会在匹配成功后立即停止。最后一个元组包含了具有五个不同优先级(从 32767 到 36866)的流,在最差的情况下,如果具有最低优先级的流匹配到这个元组,那么仍需查找其他 T.pri_max 大于 32767 的元组。

5.3、分阶段查找

元组空间搜索通过哈希表查找来搜索每一个元组。在我们构造 megaflow 匹配条件的算法中,哈希表查找意味着即使元组搜索失败了, megaflow 也必须匹配元组里的字段的所有位域,因为到目前为止,这些字段中以及其中的每一位都可能影响查找结果。当元组需要匹配一个经常变化的字段(例如 TCP 源端口),那么所生成的 megaflow 并不比安装的 microflow 有用多少,因为它只能匹配单个 TCP 流。

这是一个可以改进的地方。如果可以在元组字段的子集中进行搜索,并且通过此搜索来确定元组不可能匹配,那么生成的 megaflow 只需要匹配字段的子集,而不是元组的所有字段。

而元组是作为使用所有字段的哈希表来实现的,这使得这种优化非常困难。因为不能在哈希表的键的子集中进行搜索。所以我们考虑了其他的数据结构。字典树(前缀树)允许在字段的任意前缀中进行搜索,但同时也增加了成功搜索所需的访问内存的次数:从 O(1) 到 O(n)(n 为元组字段的长度)。针对每个字段建立哈希表也会有同样的缺点。我们不考虑那些大于 O(n) 的数据结构(n 为一个元组中流的数目),因为 OpenFlow 表可以拥有几十万个流。

我们实现的解决方案是按照流量粒度的降序顺序,将字段静态地分为四组:元数据(例如交换机的入口端口)、L2、L3 和 L4。我们将每个元组从单个哈希表修改为一个拥有四个哈希表的数组,并称之为阶段(stages):其中一个哈希表只包含了元数据字段,一个包含了元数据和 L2 字段,一个包含了元数据、L2 和 L3 字段,一个包含了所有字段。(最后一个与之前实现中的单个哈希表相同。)对一个元组的搜索会按顺序查找每个阶段。如果所有的搜索结果都不匹配,那么对元组的整体搜索也会失败,并且只有在最后一个搜索阶段中的字段才能被添加到 megaflow 匹配中。(译注:If any search turns up no match, then the overall search of the tuple also fails, and only the fields included in the stage last searched must be added to the megaflow match.)

这种优化技术可以应用于所有支持子集的字段,而不仅仅是我们所使用的这种基于层级的子集。我们按照协议层来划分字段,是因为,根据经验,在 TCP/IP 中,内层头部往往比外层头部更加多样化。例如在 L4 层,TCP 源端口和目标端口会根据每个连接而改变,但在元数据层只有相对较少数量的入口端口存在。

元组的每个阶段都包含了之前的所有阶段。我们选择了这么实现,尽管这不是必须的,因为哈希可以从一个阶段到下一个阶段进行增量计算,并且分析表明哈希计算的成本很大。

因为查找分为了四个阶段,人们可能会说查找一个元组的时间会增加四倍。我们的测试结果表明,实际上分类的速度在实践中略有提高,这是因为,在某个阶段匹配成功然后终止查找后,分类器就不必计算由该元组覆盖的所有字段的完整哈希了。

这一优化修复了在早期部署环境中出现的性能问题。NVP 控制器使用 Open vSwitch 来实现多个隔离的逻辑 datapath(进一步互连以形成逻辑网络)。每个 datapath 都是独立配置的。假设某些 datapath 配置了允许或拒绝基于 L4 (例如 TCP 或 UDP)端口号的 ACL(Access Control List,访问控制列表),这些逻辑 datapath 流量的 megaflow 必须匹配到 L4 端口号才能执行 ACL。其他 datapath 流量的 megaflow 则不需要匹配 L4 端口号(出于性能考虑,也不应该匹配)。然而,在这一优化之前,由于分类器必须查找匹配 L4 端口的所有元组,所以所有生成的 megaflow 都必须与 L4 端口匹配。而这一优化允许 megaflow 在没有 L4 ACL 的逻辑 datapath 上进行通信,以避免匹配 L4 端口,因为前三个(或更少)阶段足以确定不存在匹配。

5.4、前缀跟踪

OpenFlow 中的流通常会匹配 IPv4 和 IPv6 子网以实现路由。当所有匹配该字段的流都使用相同大小的子网时,例如全部匹配 /16 子网,则有利于构建 megaflow。另一方面,如果不同的流匹配不同大小的子网时,就像标准的 IP 路由表那样,那么所构建的 megaflow 需要匹配最长的子网前缀,例如,任一主机路由(/32)都会迫使所有 megaflow 匹配完整的地址。举例说明,假设 Open vSwitch 正在为发往 10.5.6.7 的报文构建一个 megaflow。如果同时存在与子网 10.0.0.0/8 和主机 10.1.2.3/32 相匹配的流,则可以安全地安装一个匹配 10.5.0.0/16 的 megaflow(因为 10.5.0.0/16 完全位于 10.0.0.0/8 内并且不包含 10.1.2.3),但是如果没有额外的优化,Open vSwitch 则会安装匹配 10.5.6.7/32 的 megaflow。(为了清楚起见,我们的示例仅使用八位字节前缀,例如 /8、/16、/24、/32,但在下文中所展示的实现以及伪代码则根据位前缀(bit prefix)进行工作)。

我们使用 trie(前缀树、字典树)结构来实现 IPv4 和 IPv6 字段的优化。如果流表通过 IP 地址进行匹配,分类器则需要在元组空间搜索之前对任意字段执行 LPM(longest prefix match,最长前缀匹配)搜索,以确定所需的最大 megaflow 前缀长度,以及确保哪些元组可以完全跳过且不影响正确性。举例说明,假设一个 OpenFlow 表包含了匹配某个 IPv4 字段的流,如图所示:

image

这些流对应于如下 trie 结构,其中实心圆表示上面所列的地址匹配项之一,虚线圆表示仅存在于其子项的节点:

image

为了确定要匹配的位,Open vSwitch 从根节点向下遍历 trie 中具有与报文 IP 地址中相应位相匹配的标签的节点。如果遍历到一个叶子节点,megaflow 则不需要匹配剩余的地址位,例如,在我们的例子中,10.1.3.5 将匹配 10.1.3.0/24,而 20.0.5.1 将匹配 20.0.0.0/8。另一方面,如果由于 IP 地址中的位没有匹配到 trie 中的任一标签,进而导致遍历终止,则必须构建 megaflow 以匹配这些没有找到的位,例如根据 10.3.5.1 构建 10.3.0.0/16,根据 30.10.5.2 构建 30.0.0.0/8。

通过查找 trie 的结果还允许 Open vSwitch 跳过对某些元组的搜索。考虑 10.1.6.1 这个 IP 地址。在上述例子中的 trie 对该地址进行搜索时,会在标签为 1 的节点上停止搜索,因为找不到该地址第三个八位字节的相应节点。这意味着,在流表中的流最多只能匹配该 IP 地址的前 16 位,所以分类器在查找元组时可以跳过以上列出的具有 /24 和 /32 前缀的流。

image

图 3:前缀跟踪的伪代码。该函数在以节点 root 为根节点的 trie 中搜索 value(例如,一个 IP 地址)。该函数返回从 value 开头开始必须检查的位数,以使其匹配的节点具有唯一性,并返回可能匹配长度的位数组。在伪代码中,x[i] 表示 x 的 i 位,len(x) 表示 x 的位数。

图 3 给出了前缀匹配的详细伪代码。假设每个节点都有成员变量 bits,即该节点中的位(至少有一位,除非是根节点,根节点可能为空);成员变量 leftright,分别表示节点的左右子节点(可能为空);n_rules 表示该节点的规则数量(如果当前节点是子节点,则为零,否则为非零)。该函数返回必须被匹配从而改进 megaflow 的位数,以及一个位数组,其中 0 位表明元组的规则长度为 0,Open vSwitch 可能会根据此跳过搜索。

该算法在优化了最长前缀匹配查找的同时,在即使流没有显式匹配 IP 地址前缀的情况下,也可以改善 megaflow。为了在 OpenFlow 中实现最长前缀匹配,前缀更长的流必须具有更高的优先级,这将允许 5.2 节中的元组优先级排序优化在找到最长匹配之后跳过前缀匹配表,但这本身就导致了 megaflow 无法根据表中的最长前缀进行位域通配。因此,实际上该算法的主要优点是防止应用于特定主机的策略(例如高优先级的 ACL)强制所有 megaflow 在完整 IP 地址上进行匹配。该算法允许 megaflow 条目只和高字节位匹配,同时足以识别具有 ACL 的主机的流量。

我们最终还将前缀跟踪应用在 L4 传输端口上。与 IP ACL 类似,这一实现避免了某些匹配指定传输端口的高优先级 ACL 强制所有的 megaflow 匹配完整的端口字段,这种强制匹配也会导致 megaflow 缓存失效从而在 microflow 缓存上匹配。

5.5、分类器划分

通过跳过不可能匹配的元组,可以进一步地减少元组空间搜索的数量。OpenFlow 支持在报文通过分类器的过程中对其设置和匹配元数据字段。Open vSwitch 基于特定的元数据字段对分类器进行划分。如果该字段的当前值与特定元组中的任何值都不匹配,则将完全跳过该元组。

虽然 Open vSwitch 不像传统交换机那样具有固定的流水线,但是 NVP 通常将分类器中的每一个查找都配置为流水线中的一个阶段。这些阶段会在固定数量的字段上匹配,类似于元组。NVP 通过将流水线阶段的数字指示符存储在特定的元数据字段中,以向分类器提供提示,以达到可以有效查找相关元组的目的。

6、缓存失效

缓存的另一面是管理缓存的复杂性。在 Open vSwitch 中,需要更新缓存的原因有很多。最明显的是,控制器可以修改 OpenFlow 流表。OpenFlow 也指定交换机在应对各种事件时应当自行进行修改,例如,OpenFlow “组” 行为可以取决于是否检测到网络接口载体。开启或关闭某些端口功能、新增或删除端口等之类的重配置,都可能会影响报文的处理。诸如 CFM(Connectivity Fault Management,连通性故障管理) 或 BFD(Bidirectional Forwarding Detection,双向转发检测) 这类的连通性检测协议,或者用于检测和避免环路的协议(例如(快速)生成树协议),也可以影响行为。最后,一些 OpenFlow action 和 Open vSwitch 扩展会基于网络状态(例如基于 MAC 地址学习)来改变行为。

理想情况下,Open vSwitch 可以精确识别需要为事件作出响应的 megaflow。对于某些类型的事件,这很简单。例如,当 Open vSwitch 的 MAC 地址学习实现,检测到一个 MAC 地址从一个端口转移到另一个端口,那么使用这个 MAC 的 datapath 流是需要更新的其中之一。然而 OpenFlow 模型的通用性使得在其他情况下难以进行精确识别。向 OpenFlow 表添加一个新的流就是一个例子。任何与 OpenFlow 表中优先级低于这个新流的流相匹配的 megaflow,现在都可能表现出不同的行为,但我们不知道如何有效(在时间和空间上)精确地识别这些流。一连串的 OpenFlow 流表查找进一步地恶化了这个问题。我们的结论是,在一般情况下,要求精度是不切实际的。

因此,早期版本的 Open vSwitch 将 datapath 流的行为所需要的更改划分为两组。第一组是那些影响太大而无法精确识别所需的更改,Open vSwitch 必须检查每个 datapath 流以查找可能的更改。每一个流必须像其刚开始被创建那样,以相同的方式通过 OpenFlow 流表,然后将生成的 actions 与当前安装在 datapath 上的 actions 进行对比。如果有很多 datapath 流,这可能会非常耗时,但我们在实践中发现这并不是一个问题,可能是因为只有系统实际上的网络负载很高时,才会有大量的 datapath 流,因此此时在网络处理上使用更多 CPU 是合理的。真正的问题是,因为 Open vSwitch 是单线程的,在重新检查所有 datapath 流的时间内,此时为所接收到的报文设置新流的操作会被阻塞,进而导致这些新的报文没有匹配到 datapath 流。这为这些报文的流设置增加了高延迟,大大增加了流设置延迟的总体可变性,并限制了总体流设置速率。因此,在 2.0 版本中,Open vSwitch 将缓存在 datapath 中的流的最大数量限制为 1000,并且在经过一些优化后增加到 2500,以尽量减少这些问题。

第二组由那些对 datapath 流的影响可以缩小的更改组成,例如 MAC 地址学习表的更改。Open vSwitch 的早期版本通过使用被称为 tags 的技术以优化的方式实现了这些功能。每个在更改后需要 megaflow 更新的属性,都会被添加这些 tags。同时,每个 megaflow 都会与其操作所依赖的所有属性的 tags 相关联,例如,如果一个 megaflow 的 actions 是将报文输出到端口 x 上(因为学习到报文的目标 MAC 对应都该端口上),那么 megaflow 就与该 tag 关联。稍后,如果 MAC 学习到的端口更改了,Open vSwitch 会将该 tag 添加到一组累积更改的 tags 中。Open vSwitch 分批扫描 megaflow 表,如果查找到包含这组 tags 中至少一个 tag 的 megaflow,则检查其 actions 是否需要更新。

随着时间的推移,控制器和流表都变得越来越复杂,Open vSwitch 添加了越来越多的基于网络状态而变化的 actions,每一个 datapath 流都被越来越多的 tags 所标记。我们已经将 tags 实现为布隆过滤器,这意味着每增加一个 tag 都会导致更多的“误报”用于重新验证(revalidation),所以只要任何状态更改,大部分或所有流都需要检查。在 2.0 版本中,tags 的有效性大大降低,以至于无法简化代码,Open vSwitch 完全放弃了它们,转而总是重新验证整个 datapath 流表。

因为 tags 是我们试图将流设置延迟最小化的方法之一,所以我们现在需要寻求其他方法。在 Open vSwitch 2.0 中,为了实现这个目的,我们将用户空间程序分为多个线程。我们将流设置分为不同的线程,这样它就不必等待重新验证。然而,datapath 流的逐出(eviction),仍然是单个主线程的一部分,并且无法跟上多线程流设置的步伐。然而,在流设置的负载很大时,逐出的速率是至关重要的,因为用户空间程序必须能够以安装新流的速度从 datapath 中删除流,否则 datapath 的缓存很快就被填满。因此,在 Open vSwitch 2.1 中,引入了多个专门用于缓存重新验证(cache revalidation)的线程,这允许我们提升了重新验证的性能,以匹配流设置的性能,并将内核缓存的最大值提升到 200000 个条目。实际的最大值是动态调整的,以确保总的重新验证时间保持在 1 秒以下,以限制过期条目可以保留在缓存中的时间。

Open vSwitch 用户空间程序通过定期(大约每秒一次)轮询内核模块中每个流的报文和字节计数器,来获取 datapath 缓存统计信息。datapath 流统计信息的主要用途是确定哪些 datapath 流是有用的并且应该保留在内核中,哪些 datapath 流并没有处理多少报文,应该被逐出。如果当前表中流的数量远小于表的最大值,那么流可以一直保留在内核中,直到它们超过一段空闲时间(时间可配置,现在默认为 10 秒)。如果当前表中流的数量超过了最大值,那么 Open vSwitch 会降低这个空闲时间以强制减小表的大小。定期轮询内核中每个流的统计信息的线程,也会使用这些统计信息来实现 OpenFlow 中单个流报文和字节计数统计以及流的空闲超时功能。这意味着 OpenFlow 的统计信息只是定期更新的。

上文描述了用户空间程序是如何导致 datapath megaflow 缓存失效的。一级 microflow 缓存(在第 4 节中讨论)的维护要简单得多。microflow 缓存项仅仅是元组空间搜索中第一个哈希表的提示。因此,当报文第一次匹配过期的 microflow 缓存项时,后者就会被检测到并更正。microflow 缓存具有固定的最大值,新的 microflow 替换旧的 microflow,因此无需定期刷新新旧条目。为了简单起见,我们使用了伪随机替换策略,并发现它在实践中是有效的。

7、评估

以下的章节将检验 Open vSwitch 在生产环境以及微型基准测试中的性能。

7.1、生产环境中的性能

我们检验了由 Rackspace 公司运营的大型商业多租户数据中心中的虚拟机监控程序所提供的 24 小时 Open vSwitch 性能数据。我们的数据集包含了每 10 分钟从 1000 多个运行 Open vSwitch 的虚拟机监控程序中收集到的统计数据,这些 Open vSwitch 用于在网络虚拟化设置中为混合租户提供工作负载。

image

图 4:观察到的最小/平均/最大 megaflow 流的数量。

缓存大小。从当前活跃的 megaflow 数量,我们可以看出 Open vSwitch 实际控制的 megaflow 的缓存大小。在图 4,我们展示了观察期间内最小、平均和最大数量的累积分布函数。图中显示在实践中小型的 megaflow 缓存就足够了:50% 的虚拟机监控程序的流的平均数量为 107 或更小。即使是最大流的情况,在第 99 个百分位也只有 7033 个流。对于这种环境下的虚拟机监控程序,Open vSwitch 用户空间可以维护足够大的内核缓存。(在最新的 Open vSwitch 的主流版本中,内核流的数目限制为 200000。)

image

图 5:所有时间内(实线)、最忙期间(虚线)和最慢期间(点线)的缓存命中率。

缓存命中率。图 5 展示了缓存的有效性。实线展示了整个虚拟机监控程序集群中每 10 分钟测量间隔的总体缓存命中率。总体缓存命中率为 97.7%。点线展示了测量周期中转发报文数最少的情况(占整个测量周期的 25%),其中缓存的效率低于总体,缓存命中率为 74.7%。直观地说,当没有什么可缓存时,缓存的效率较低(同时缓存也不重要)。Open vSwitch 的缓存在最有用时最有效:什么时候最有用?即当有大流量需要缓存时。虚线展示了测量周期中转发报文数最多的情况(占整个测量周期的 25%),也说明了这一点:在转发报文数最多的情况下,缓存命中率略高于总体平均水平,达到 98.0%。

image

图 6:缓存命中(实线)和未命中(虚线)的报文数量。

该数据中心中的绝大多数虚拟机监控程序都不会经受来自其工作负载的高流量。图 6 描述了这一点:99% 的虚拟机监控程序的缓存命中的报文数少于 79000 个/秒(由于缓存未命中而进入用户空间的流设置数少于 1500 个/秒)。

CPU 使用率。在统计数据的收集过程中,我们无法将 Open vSwitch 的内核负载与操作系统内核负载区分开,因此我们将重点放在 Open vSwitch 的用户空间上。如我们将在 7.2 节中所示,megaflow 本身的 CPU 使用率与 Linux 网桥差不多,这不是值得担忧的。在 Open vSwitch 中,用户空间负载主要是由于内核的缓存未命中,图 7 描述了这一点。(由于多线程的存在,用户空间的 CPU 负载可能超过 100%。)我们观察到,80% 的虚拟机监控程序消耗在 ovs-vswitchd 的平均 CPU 使用率为 5% 或更低,这一直是我们的目标。超过 50% 的虚拟机监控程序只消耗 2% 或更低的 CPU。

image

图 7:随着进入用户空间的每秒未命中数而变化的用户空间守护进程 CPU 负载。

极端情况。图 7 的右上角描述了虚拟机监控程序使用大量 CPU 来处理用户空间大量未命中的情况。我们分别检验了六种最极端的情况,在这些情况中 Open vSwitch 24 小时内的平均 CPU 使用率都超过了 100%。我们发现,所有这些虚拟机监控程序在前缀跟踪的实现中都出现了以前未知的 bug,例如,匹配 ICMP 类型的流会导致所有 TCP 流分别在整个 TCP 源端口或目标端口上匹配。我们已经在 Open vSwitch 2.3 版本中修复了这个 bug,但是数据中心还没有及时升级以及在生产环境中验证。

7.2、缓存微型基准测试

我们使用了一个简单的流表来运行微型基准测试,该流表旨在简洁地展示缓存感知报文分类算法的优点。我们使用了如下 OpenFlow 流,优先级是从高到低,同时省略了这些流的 actions,因为这些 actions 对讨论没有意义:

arp (1)
ip ip_dst=11.1.1.1/16 (2)
tcp ip_dst=9.1.1.1 tcp_src=10 tcp_dst=10 (3)
ip ip_dst=9.1.1.1/24 (4)

在没有缓存感知报文分类的情况下,任何 TCP 报文都会生成一个匹配 TCP 源端口和目标端口的 megaflow,因为流 #3 匹配这些字段。在使用优先级排序(第 5.2 节)的情况下,匹配流 #2 的报文可以省略在 TCP 端口上的匹配,因为流 #3 是不会被考虑到的。在使用分阶段查找(第 5.3 节)的情况下,目标 IP 不为 9.1.1.1 的 IP 报文永远不需要在 TCP 端口上匹配,因为在只考虑 IP 目标地址后,流 #3 被标记为不匹配。最后,地址前缀跟踪(第 5.4 节)使得 megaflow 忽略 IP 目标地址中的某些位,即使流 #3 匹配整个 IP 地址。

缓存层性能。我们首先测量了每个 Open vSwitch 缓存层的基线性能。在下列所有测试中,Open vSwitch 都是运行在有两个 8 核心、2.0 GHz Xeon 处理器以及两张 Intel 10 Gb 网卡的 Linux 服务器上。为了生成多个连接,我们使用了 Netperf 的 TCP_CRR 测试,该测试会重复建立 TCP 连接并收发单字节流量,然后断开连接。测试结果以每秒事务数(tps)的形式报告。Netperf 一次性只进行一个连接尝试,因此我们并行运行了 400 个 Netperf 然后进行了汇总。

Optimizations ktps Flows Masks CPU%
Megaflows disabled 37 1,051,884 1 45/ 40
No optimizations 56 905,758 3 37/ 40
Priority sorting only 57 794,124 4 39/ 45
Prefix tracking only 95 13 10 0/ 15
Staged lookup only 115 14 13 0/ 15
All optimizations 117 15 14 0/ 20

表 1:分类器优化的性能测试结果。每一行内容是每秒测量的 Netperf TCP_CRR 事务数(以千为单位)、内核流数、内核掩码数、用户空间/内核空间的 CPU 使用率。

Microflows Optimizations ktps Tuples/pkt CPU%
Enabled Enabled 120 1.68 0/ 20
Disabled Enabled 92 3.21 0/ 18
Enabled Disabled 56 1.29 38/ 40
Disabled Disabled 56 2.45 40/ 42

表 2:microflow 缓存的影响。每一行内容是每秒测量的 Netperf TCP_CRR 事务数(以千为单位)、每个报文搜索的平均元组数、用户空间/内核空间的 CPU 使用率。

为了测量 Open vSwitch 用户空间程序的报文处理的性能,我们通过在 datapath 中只设置 microflow 的方式,将 ovs-vswitchd 配置为禁用 megaflow 缓存。如表 1 所示,在 TCP_CRR 测试中产生了 37 ktps,有超过一百万个内核流条目,并使用了大约 1 个 CPU 核心的时间。

为了量化 megaflow 缓存本身的吞吐量,我们重新启用了 megaflow 缓存,然后禁用了内核 microflow 缓存。表 2 显示,当启用了分类器优化时,禁用 microflow 缓存会导致 TCP_CRR 性能从 120 ktps 降低到 92 ktps。(当禁用分类器优化时,禁用 microflow 缓存几乎没有效果,因为其影响会被用户空间访问次数的增加所掩盖。)

image

图 8:在禁用 microflow 缓存的情况下,根据查找 megaflow 元组的平均数而变化的转发速率。

图 8 展示了在禁用 microflow 缓存的情况下,随着查找元组的平均数而改变的长连接(longlived)流的报文转发性能。在相同的场景中,启用 microflow 缓存之后,我们测量了长连接(longlived)流的报文转发性能约为 10.6 Mpps,并且不受内核分类器中的元组数影响。即使平均只搜索 5 个元组,microflow 缓存也可以将性能提高 1.5 Mpps,可见其价值。为了从原始的哈希查找性能的角度来看这些数字,我们单独对元组空间分类器进行了基准测试:通过一个包含 50 万个流的随机生成的表,该实现可以在单个 CPU 核心上以大约每秒 680 万个哈希查找的速度执行,这相当于每秒在 10 个元组中进行 680000 个分类。

分类器优化所带来的效益。我们衡量了分类器优化所带来的效益。表 1 展示了单个优化和所有优化的改进。每次优化都会减少运行测试所需的内核流的数量。每个内核流对应于内核与用户空间之间的一次交互,因此流的减少也会减少用户空间的 CPU 时间。从表 1 可以看出,随着内核流数量(Flows)的减少,内核流表(Masks)中的元组数量增加,这会增加内核分类的成本,但内核 CPU 时间的减少和 TCP_CRR 的增加表明,microflow 缓存和减少的用户空间访问抵消了这一点。TCP_CRR 测试对延迟高度敏感,因此可见延迟也在减少。

与内核交换机的比较。我们将 Open vSwitch 与 Linux 网桥(完全在 Linux 内核中实现的以太网交换机)进行了比较。在使用最简单的配置的情况下,这两个交换机能达到相同的吞吐量(18.8 Gbps)和相似的 TCP_CRR 连接速率(Open vSwitch 为 696 ktps,Linux 网桥为 688 ktps),尽管 Open vSwitch 使用了更多的 CPU(161% 对 48%)。然而,当我们向 Open vSwitch 添加一个用于丢弃 STP BPDU 报文的流,以及添加类似的 iptables 规则到 Linux 网桥时,Open vSwitch 的性能和 CPU 使用率保持不变,而 Linux 网桥的连接速率下降到 512 ktps,其 CPU 使用率增加了 26 倍,达到 1279%。这是因为内置的内核函数对每个报文都有开销,而 Open vSwitch 的开销通常是固定的。我们预计如果启用其他功能,如路由和防火墙,也会同样增加 CPU 负载。

8、正在进行的、未来的和相关的工作

现在,我们将简要地讨论我们目前和计划中 Open vSwitch 的改进工作,并简要介绍相关工作。

8.1、有状态报文处理

OpenFlow 不支持有状态报文操作,因此,每个连接或每个报文转发状态都需要控制器参与。为此,除了远程的主控制器之外,Open vSwitch 还允许在虚拟机监控程序运行“本地控制器”。因为本地控制器是一个任意程序,所以它可以在 Open vSwitch 发送的报文中保持任意数量的状态。例如,NVP 就包含了一个本地控制器,该控制器实现了负责发送和处理 ARP 的有状态 L3 守护进程。这个 L3 守护进程将 L3 ARP 缓存填充到专用 OpenFlow 表(不由主控制器管理)中,以在常见情况下(具有已知 IP-MAC 绑定的报文)进行快速转发。这个 L3 守护进程只接收导致 ARP 缓存未命中的报文,并根据从 Open vSwitch 接收到的报文,向远程 L3 守护进程发出任意必要的 ARP 请求。虽然本地控制器与 Open vSwitch 之间的连接是本地的,但性能开销很大:接收到的报文首先从内核(datapath)传送到用户空间守护进程(ovs-vswitchd),然后通过本地套接字(同样需要通过内核)传送到单独的进程(本地控制器)。

对于对性能要求很高的有状态报文操作,Open vSwitch 依赖于内核网络的支持。例如,一个可靠的 IP 隧道实现需要(有状态的)IP 重组支持。类似的,传输连接跟踪是基本 L2/L3 之后的第一个实际需求;即使是最基本的防火墙安全策略也需要有状态过滤。OpenFlow 可以实现静态 ACL,但不能实现有状态 ACL。为此,我们正在努力提供一个新的 OpenFlow 操作,该操作调用一个提供元数据的内核模块,后续 OpenFlow 表可以通过元数据中的连接状态(新的、已建立的、相关的)来作出转发决策。这个“连接跟踪”与许多专用防火墙设备中使用的技术相同。在内核网络栈与内核 datapath 模块之间进行转换会产生开销,但可以避免功能重复,这对升级内核至关重要。

8.2、用户空间网络

由于 NFV 的出现,通过用户空间网络来提高虚拟交换机的性能是一个适时的话题。在此模型中,以虚拟机监控程序用户空间/内核最小程度的干预,将报文直接从网卡传递到虚拟机,通常是通过网卡、虚拟交换机和虚拟机之间的共享内存实现。为此,目前正在努力将对 DPDK 和 netmap 的支持添加到 Open vSwitch 中。早期的测试表明,在这种情况下,Open vSwitch 的缓存体系结构对内核流缓存同样有益。

Linux 社区中的一些人正在研究 DPDK 的另一种替代方法,是减少通过内核的开销。特别是,在 Linux 内核中存储报文的 SKB 结构的大小为好几个缓存线(cache line),这与 DPDK 和 netmap 中的紧凑表示形式相反。我们期望 Linux 社区在这方面可以做出重大改进。

8.3、硬件卸载

随着时间的推移,网卡为常用功能添加了硬件卸载,这些功能会占用过多的主机 CPU 时间。其中的一些功能,如 TCP 检验和与 TCP 分段卸载,随着时间的推移,已经证明非常有效。Open vSwitch 利用了这些硬件卸载以及其他的、与虚拟化环境相关的硬件卸载。不过事实证明,针对虚拟化环境的专用硬件卸载更难实现。

将虚拟交换功能完全转移到硬件上,是一个反复出现的主题。这种技术的优点是高性能,但代价是牺牲了灵活性:一个简单的固定功能的硬件交换机可以有效地取代软件虚拟交换机,然而虚拟机监控程序无法扩展其功能。我们目前发现最有效的卸载方法是使得网卡能够加速内核流分类。某些 Intel 网卡的 Flow Director 功能已被证明对于将报文分类到单独的队列非常有用。增加此功能只是报告匹配规则,而不是选择队列,这将使其对 megaflow 分类非常有用。即使 TCAM 大小收到限制,或者如果 TCAM 不支持 datapath 使用的所有字段,它也可以通过减少哈希表搜索的数量来加速软件分类的速度,而不会限制灵活性,因为这些操作仍将在主机 CPU 中进行。

8.4、相关工作

流缓存。流缓存的好处已经被社区中的许多人争论过了。有人描述了如何使用软件流缓存来增加硬件交换机流表的有限容量,但未提及不同形式或优先级的流的问题。CacheFlow,与 Open vSwitch 类似,将一组 OpenFlow 流缓存在快速路径,但 CacheFlow 要求快速路径直接实现所有 OpenFlow 操作,并需要构建提前创建完整的流依赖关系图。

报文分类。分类是一个已经被研究得很透彻的问题。许多分类算法要么只适用于静态流集合,要么增量更新过程的开销很大,因此不适用于动态 OpenFlow 流表。有些分类器所需的内存大小为流数量的指数倍。其他分类器只能处理 2 到 5 个字段,而 OpenFlow 1.0 有 12 个字段,更高版本有更多字段。

9、总结

我们描述了 Open vSwitch 的设计与实现,这是一款开源、多平台的 OpenFlow 虚拟交换机。Open vSwitch 的起源很简单,但其性能已逐步优化,以匹配多租户数据中心工作负载的要求,这需要更复杂的设计。考虑到它的运行环境,我们预计不会有任何变化,但随着时间的推移,它的设计只会与传统的网络设备更加不同。

参考资料

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注