主机网络
什么是主机网络?
传统上,网络的瓶颈存在于网络基础设施内部,而非终端主机。然而,在现代高性能数据中心中,随着网络性能需求的不断增长,终端主机正难以跟上这一需求。
特别是,运行 TCP 等网络协议的 CPU 已无法提供数据中心所需的高性能。CPU 成本高昂,而要实现高性能,意味着 CPU 需将所有时间用于运行网络协议,分配给实际应用程序的资源则会减少。
此外,我们一直在使用的实际协议(如 IP 和 TCP)也已无法满足现代高性能需求。
为解决这两个问题,我们转向主机网络,它涉及在终端主机(而非网络内部)进行优化。

优化:用户空间中的共享内存
回想一下,在终端主机上,第 1 层和第 2 层由网络接口卡(NIC)以硬件方式实现。第 3 层和第 4 层由操作系统(在 CPU 上)以软件方式实现。第 7 层是应用程序本身。

回顾先修课程(例如加州大学伯克利分校的 CS 61C)可知,现代计算机采用虚拟内存设计,因此每个应用程序都有自己专用的地址空间,与其他应用程序相互隔离。特别是,每个第 7 层应用程序在用户空间中都有自己专用的地址空间。相比之下,操作系统本身运行在内核空间,这是用户空间中的应用程序无法访问的特殊内存区域。
这种内存管理模型意味着,当我们将数据包沿协议栈向下传递以发送数据时,需要不断将数据从用户空间复制到内核空间。同样,当我们将数据包沿协议栈向上传递以接收数据时,也需要不断将数据从内核空间复制到用户空间。在内核空间和用户空间之间复制数据既耗时又无意义。
这种内存管理模型的另一个问题是,在内核空间中编程难度较大。如果我们想修改 TCP 并对其进行优化,就必须深入操作系统,在极低的层级进行编程。在内核空间中进行部署和测试比在用户空间中更困难、更缓慢。

为解决这两个问题,我们可以将网络协议栈(如第 3 层和第 4 层协议)从内核空间移至用户空间。这样,第 3 层、第 4 层和第 7 层就都能访问共享地址空间,无需来回复制数据。此外,在用户空间中进行迭代和创新也变得更加容易。

在用户空间中使用共享内存有助于我们消除一些额外工作(如来回复制数据),但这仍不足以使我们的主机满足现代性能要求。
优化:卸载到 NIC
CPU 的速度不足以以现代性能速度运行网络协议(如 IP、TCP)。此外,使用 CPU 运行网络协议会减少应用程序可使用的 CPU 资源。
为解决这一问题,我们可以将网络协议栈从 CPU(软件)卸载到 NIC(硬件)。
NIC 是卸载操作的理想场所。每个数据包都必须经过 NIC,因此 NIC 可以执行一些额外处理,从而节省 CPU 的工作。

网络驱动程序是操作系统中用于编程和管理 NIC 的一段软件。驱动程序提供了一个 API,允许操作系统中的高层程序与 NIC 进行交互。可以将驱动程序视为硬件和软件之间的桥梁。
卸载有哪些好处呢?它释放了 CPU 资源供应用程序使用。此外,硬件中的专用处理可能比通用 CPU 上的处理更高效。这里的高效既指速度,也指功耗。最后,在硬件中运行操作不仅能降低延迟,还能实现更可预测和更一致的延迟。在软件中运行应用程序时,CPU 必须调度不同的进程,这可能会增加不可预测的延迟。(例如,如果有一个数据包需要处理,CPU 可能必须完成当前任务后才能切换到处理该数据包。)
卸载的简要历史:第 0 时代
将操作从操作系统(软件)卸载到 NIC(硬件)是一个活跃且持续的研究领域。其发展经历了三个时代,越来越复杂的操作被卸载到 NIC。
第 0 时代:在没有任何卸载之前,让我们看看在我们目前所了解的标准网络协议栈中,NIC 的作用是什么。
NIC 有一个中央控制器处理器,用于管理卡上的操作。
对于传入的数据包,收发器将电信号转换为数字信号(0 和 1),并将这些比特放入缓冲区。然后,NIC 从缓冲区读取比特,将其解析为以太网帧,处理该帧(如验证校验和),并移除第 2 层头部。最后,NIC 生成一个中断,告知 CPU 停止当前工作,收集生成的第 3 层数据包以进行进一步处理。
对于传出的数据包,来自网络驱动程序的数据包被放入缓冲区。NIC 从缓冲区读取比特,并对其进行处理以构建以太网帧。然后,该帧被传递给收发器,收发器将数字比特转换为电信号。

在标准网络协议栈中,可以将 NIC 视为一个门垫,它将传入的数据包传递给操作系统,并为操作系统发送传出的数据包,但对这些数据包只执行极少的处理。
卸载的简要历史:第 1 时代

我们尝试卸载到 NIC 的首批操作是简单的无状态操作。这些无状态操作可以针对每个数据包独立进行,NIC 无需记住多个数据包之间的任何状态。
我们可以卸载的一种无状态操作是校验和计算,不仅是第 2 层的校验和,还包括第 3 层和第 4 层的校验和。NIC 可以验证这些校验和(对于传入的数据包)并计算这些校验和(对于传出的数据包),这样 CPU 就无需执行这些操作。
另一种可以卸载的无状态操作是分段。在我们的标准模型中,如果应用程序要发送一个大文件,操作系统负责将文件分割成更小的数据包。然后,在接收方,操作系统负责重组这些数据包。作为一种优化,我们可以让 NIC 负责分割和重组数据包。现在,操作系统不再需要处理大量的小数据包,而是可以处理少量的大数据包,这更高效(例如,需要处理的头部更少)。

对于分段,在连接平滑性和 CPU 效率之间存在权衡。如果应用程序将大数据包交给 NIC,CPU 的工作量会减少。然而,NIC 现在会收到大量突发数据,连接会更具突发性。相比之下,如果应用程序将小数据包交给 NIC,CPU 的工作量会增加,但 NIC 会收到更稳定的数据流,由此产生的连接会更平滑。
聚合小数据包存在一些挑战。如果中间的某个数据包丢失了怎么办?那么 NIC 可能不得不向上传递一堆小数据包,而无法将它们组合成一个大数据包。如果有些数据包设置了某个标志(如拥塞的 ECN),而其他数据包没有设置,该怎么办?由此产生的聚合数据包应该设置该标志还是不设置?
我们要介绍的第三种可卸载的无状态操作是多队列支持。在我们的标准模型中,NIC 有一个用于传出数据包的队列和一个用于传入数据包的队列,所有应用程序共享这些队列。网络驱动程序(在软件中)负责负载均衡,以防多个应用程序或多个 CPU 在发送和接收数据。
我们可以将这种负载均衡工作卸载到 NIC。现在,NIC 有多个发送队列和多个接收队列。例如,在多处理器系统中,每个 CPU 可以有自己专用的发送 / 接收队列。NIC 并行维护所有队列,确保不同 CPU 之间的隔离和负载均衡。NIC 还可以优先处理某些队列。
尽管 NIC 有多个队列,但它最终仍然必须通过一根线发送所有数据包。因此,NIC 需要一些数据包调度器来决定下一个从哪个队列发送。调度器可以被编程以实现所需的负载均衡行为(例如,如果我们想优先处理某个队列)。

多队列的一个挑战是将数据包映射到队列。当 CPU 有数据要发送时,它应该使用哪个队列?特别是,我们希望确保单个流中的所有数据包都进入同一个队列(而不是分散到多个队列中)。这有助于我们确保流中的数据包按顺序发送。回想一下,在 TCP 中,乱序发送数据包是可行的,但对性能不利(例如,接收方必须缓冲乱序数据包)。
在处理来自各个接收队列的传入数据包时,NIC 可以对数据包进行哈希处理,以决定由哪个 CPU 处理该传入数据包。然后,NIC 中断该 CPU,告知其处理该数据包。这种基于哈希的行为类似于 ECMP(等成本多路径路由),有助于确保同一流中的所有数据包由同一个 CPU 按顺序处理。
卸载的简要历史:第 2 时代
后来,我们开始将更复杂的有状态操作卸载到 NIC。
第 2 时代的发展是由数据中心的虚拟化推动的,在数据中心中,多个虚拟机在同一台物理服务器上运行。例如,在虚拟化中,我们需要一个虚拟交换机将传入的数据包转发到相应的虚拟机。我们展示过在软件中运行的虚拟交换机,但虚拟交换机也可以在硬件中实现。
防火墙和带宽管理是有状态卸载的另一个例子。在软件中,我们可以实现一个防火墙来执行安全策略(如丢弃来自这个恶意 IP 的所有传入数据包)。我们还可以执行策略来管理用户之间的带宽(如用户 A 每分钟只能发送 100 个数据包,超出部分将被丢弃)。这些安全策略可以由硬件来检查。
为实现这些有状态操作,我们可以使用类似于 OpenFlow 表(来自 SDN 部分)的匹配 - 动作对表。这个 API 允许软件在硬件上编程不同的策略,以便硬件可以根据这些策略处理数据包。如前所述,匹配可以针对 5 元组或其他一些头部字段。动作可以是丢弃数据包、将数据包转发到特定的下一跳或修改头部。

卸载的简要历史:第 3 时代
这是当前的卸载时代。人们正在努力将整个协议(如 TCP)从操作系统卸载到 NIC。这个时代是由更高的性能需求推动的,特别是对于具有高性能要求的应用程序,如人工智能 / 机器学习(AI/ML)。

理想情况下,我们希望让应用程序直接将数据交给硬件,让硬件在第 4 层、第 3 层、第 2 层和第 1 层执行所有必要的网络处理。操作系统完全不参与,所有网络协议都直接在硬件中实现。
虽然已经有一些将标准网络协议(如 TCP)卸载到 NIC 的实验,但它们尚未大规模部署。相反,我们设计了新的网络协议,如 RDMA,这些协议是专门为允许直接在硬件中实现而设计的。
RDMA:远程直接内存访问
RDMA 提供了一种抽象,即服务器 A 可以直接访问服务器 B 中的内存,而无需两个服务器中的操作系统或 CPU 参与。RDMA 可以直接在硬件中实现,取代标准的 TCP/IP 软件网络协议栈。
假设服务器 A 要向服务器 B 发送一个 10GB 的文件。在标准网络协议栈中,CPU 从内存中读取文件,对其进行处理(如 TCP/IP),并将生成的数据包传递给 NIC。在接收方,NIC 将数据包传递给 CPU,CPU 处理这些数据包,并将生成的文件有效载荷写入内存。请注意,CPU 需要参与处理这个 10GB 文件的每个数据包。

在 RDMA 抽象中,NIC 从内存中读取文件并发送出去,无需 CPU 参与。在接收方,NIC 处理传入的字节并将它们写入内存,同样无需 CPU 参与。需要注意的是,开始时仍需要 CPU 来设置传输,结束时也需要 CPU 来完成传输。但 10GB 文件传输的主要部分无需 CPU 参与。

要使用 RDMA,程序员不再使用套接字抽象。相反,我们使用的主要抽象是队列对。发送工作队列包含所有需要将数据从我传输到其他人的待处理任务。接收工作队列包含所有需要从其他人接收数据的待处理任务。一个 NIC 可以有多个队列对,每个队列对为程序员提供不同的服务。例如,一个队列对可能提供可靠的、按顺序的交付,而另一个队列对可能提供不可靠的交付。配置为可靠且按顺序的队列对最接近传统的 TCP 连接。

队列中的每个元素称为工作队列元素(WQE)。WQE 让应用程序描述需要完成的工作。通俗地说,接收队列中的 WQE 可能会说:“从远程服务器上地址 0xffff1234 开始获取 100MB 数据,并将它们写入我本地内存中的地址 0xffff7890。” 在代码中,WQE 是一个包含这些指令的结构体,例如指向我们要写入接收数据位置的指针。

请注意,WQE 抽象为 RDMA 协议提供了对应用程序的更高层次的视图。在 TCP/IP 协议栈中,网络只看到字节流,但在 RDMA 中,WQE 允许应用程序更详细地描述任务(例如,指定正在传输的数据块的开始和结束)。
当一个任务完成时,WQE 从队列中移除,NIC 创建一个新的结构体,称为完成队列元素(CQE),描述任务的执行情况(如成功或失败)。这个 CQE 存储在完成队列中,等待应用程序准备好读取 CQE 并了解任务的执行情况。

请注意,RDMA 是异步的。应用程序可以随时将任务(WQE)添加到队列对中,NIC 将按顺序处理这些任务。同样,当任务完成时,CQE 被放置在完成队列中,应用程序可以随时读取 CQE。(与此形成对比的是 TCP/IP 协议栈,在该协议栈中,传入的数据会触发中断,让 CPU 处理该数据。)
RDMA 示例
RDMA 可用于服务器之间的几种不同操作。每种操作都有其自身的性能规格(如不同的延迟)和不同的语义(如不同的错误消息)。例如,让我们看看 RDMA 发送操作,其中服务器 A 从其内存中读取一个文件,传输该数据,服务器 B 将该文件写入其内存。
- 每个服务器指定其内存的某个部分可供 NIC 用于 RDMA 传输。服务器 A 将与文件对应的内存指定为 NIC 可读取。服务器 B 将一个空白缓冲区(用于接收文件)指定为 NIC 可写入。

- 每个服务器设置队列。两个 NIC 现在都有一个发送队列、一个接收队列和一个完成队列。请注意,这一步可以通过传统协议(如 TCP)在服务器之间进行协调,以带外方式完成。

- 服务器 A 在发送队列中创建一个 WQE。这个 WQE 包含指向文件的指针,指示要发送的数据。在另一端,服务器 B 在接收队列中创建一个 WQE。这个 WQE 包含指向空白缓冲区的指针,指示接收的数据应写入的位置。


- 一旦双方都将传输任务加入队列,数据传输就可以在没有软件参与的情况下进行。NIC 处理所有事情,包括可靠性、拥塞控制等。

- 传输完成后,WQE 从队列中移除。两个 NIC 都生成一个 CQE,指示传输已完成,并包含任何相关的状态消息(如错误消息)。服务器 A 的 CQE 指示数据已成功发送,服务器 B 的 CQE 指示数据已成功接收。

- 最终,应用程序读取 CQE 以了解传输的情况。

RDMA 的优缺点及应用
RDMA 为我们提供了高性能的数据传输(低延迟、高带宽),并为应用程序释放了 CPU 资源。然而,RDMA 并非没有代价。RDMA 需要专用的硬件和软件,通常比传统的网络协议栈更复杂。请记住,RDMA 正在取代 TCP/IP 协议栈,因此它必须直接在硬件中实现所有 TCP/IP 功能,如可靠性和拥塞控制。
RDMA 也有一些局限性,通常在两个服务器物理位置较近的数据中心中工作得最好。如果两个服务器相距较远,主要延迟来自于通过网络发送数据,RDMA 带来的时间节省可以忽略不计。相比之下,如果两个服务器距离较近,主机处理数据包可能是主要延迟,因此 RDMA 可以带来显著的时间节省。
RDMA 已应用于许多需要高性能、低延迟计算的场景。例如科学研究、金融建模、天气预报、机器学习和搜索查询。在云计算中,RDMA 可用于将大型虚拟机从一台物理服务器迁移到另一台,为客户释放 CPU 资源。在 AI/ML 训练中,RDMA 不仅释放了 CPU 并提供低延迟,还提供了可预测的延迟,这在不同服务器需要协同训练 AI/ML 模型时非常重要。
实现 RDMA
请记住,RDMA 取代了 TCP/IP 网络协议栈,因此 RDMA 负责可靠性、拥塞控制等。实现这些功能有两种主要思路。
一种选择是在网络本身中实现这些功能,例如在交换机中实现可靠性。这是英伟达 InfiniBand 背后的理念。
另一种选择是在 NIC 中、队列对抽象之下实现这些功能。这是谷歌目前正在探索的理念。
在这两种情况下,软件中的应用程序和操作系统都通过队列对抽象获得可靠的、按顺序交付的假象。不同之处在于 RDMA 实际如何实现这些服务保证。
(注:文档部分内容可能由 AI 生成)