目录 ← 首页
CS168

主机网络

什么是主机网络?

传统上,网络的瓶颈存在于网络基础设施内部,而非终端主机。然而,在现代高性能数据中心中,随着网络性能需求的不断增长,终端主机正难以跟上这一需求。

特别是,运行 TCP 等网络协议的 CPU 已无法提供数据中心所需的高性能。CPU 成本高昂,而要实现高性能,意味着 CPU 需将所有时间用于运行网络协议,分配给实际应用程序的资源则会减少。

此外,我们一直在使用的实际协议(如 IP 和 TCP)也已无法满足现代高性能需求。

为解决这两个问题,我们转向主机网络,它涉及在终端主机(而非网络内部)进行优化。

img

优化:用户空间中的共享内存

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

img

回顾先修课程(例如加州大学伯克利分校的 CS 61C)可知,现代计算机采用虚拟内存设计,因此每个应用程序都有自己专用的地址空间,与其他应用程序相互隔离。特别是,每个第 7 层应用程序在用户空间中都有自己专用的地址空间。相比之下,操作系统本身运行在内核空间,这是用户空间中的应用程序无法访问的特殊内存区域。

这种内存管理模型意味着,当我们将数据包沿协议栈向下传递以发送数据时,需要不断将数据从用户空间复制到内核空间。同样,当我们将数据包沿协议栈向上传递以接收数据时,也需要不断将数据从内核空间复制到用户空间。在内核空间和用户空间之间复制数据既耗时又无意义。

这种内存管理模型的另一个问题是,在内核空间中编程难度较大。如果我们想修改 TCP 并对其进行优化,就必须深入操作系统,在极低的层级进行编程。在内核空间中进行部署和测试比在用户空间中更困难、更缓慢。

img

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

img

在用户空间中使用共享内存有助于我们消除一些额外工作(如来回复制数据),但这仍不足以使我们的主机满足现代性能要求。

优化:卸载到 NIC

CPU 的速度不足以以现代性能速度运行网络协议(如 IP、TCP)。此外,使用 CPU 运行网络协议会减少应用程序可使用的 CPU 资源。

为解决这一问题,我们可以将网络协议栈从 CPU(软件)卸载到 NIC(硬件)。

NIC 是卸载操作的理想场所。每个数据包都必须经过 NIC,因此 NIC 可以执行一些额外处理,从而节省 CPU 的工作。

img

网络驱动程序是操作系统中用于编程和管理 NIC 的一段软件。驱动程序提供了一个 API,允许操作系统中的高层程序与 NIC 进行交互。可以将驱动程序视为硬件和软件之间的桥梁。

卸载有哪些好处呢?它释放了 CPU 资源供应用程序使用。此外,硬件中的专用处理可能比通用 CPU 上的处理更高效。这里的高效既指速度,也指功耗。最后,在硬件中运行操作不仅能降低延迟,还能实现更可预测和更一致的延迟。在软件中运行应用程序时,CPU 必须调度不同的进程,这可能会增加不可预测的延迟。(例如,如果有一个数据包需要处理,CPU 可能必须完成当前任务后才能切换到处理该数据包。)

卸载的简要历史:第 0 时代

将操作从操作系统(软件)卸载到 NIC(硬件)是一个活跃且持续的研究领域。其发展经历了三个时代,越来越复杂的操作被卸载到 NIC。

第 0 时代:在没有任何卸载之前,让我们看看在我们目前所了解的标准网络协议栈中,NIC 的作用是什么。

NIC 有一个中央控制器处理器,用于管理卡上的操作。

对于传入的数据包,收发器将电信号转换为数字信号(0 和 1),并将这些比特放入缓冲区。然后,NIC 从缓冲区读取比特,将其解析为以太网帧,处理该帧(如验证校验和),并移除第 2 层头部。最后,NIC 生成一个中断,告知 CPU 停止当前工作,收集生成的第 3 层数据包以进行进一步处理。

对于传出的数据包,来自网络驱动程序的数据包被放入缓冲区。NIC 从缓冲区读取比特,并对其进行处理以构建以太网帧。然后,该帧被传递给收发器,收发器将数字比特转换为电信号。

img

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

卸载的简要历史:第 1 时代

img

我们尝试卸载到 NIC 的首批操作是简单的无状态操作。这些无状态操作可以针对每个数据包独立进行,NIC 无需记住多个数据包之间的任何状态。

我们可以卸载的一种无状态操作是校验和计算,不仅是第 2 层的校验和,还包括第 3 层和第 4 层的校验和。NIC 可以验证这些校验和(对于传入的数据包)并计算这些校验和(对于传出的数据包),这样 CPU 就无需执行这些操作。

另一种可以卸载的无状态操作是分段。在我们的标准模型中,如果应用程序要发送一个大文件,操作系统负责将文件分割成更小的数据包。然后,在接收方,操作系统负责重组这些数据包。作为一种优化,我们可以让 NIC 负责分割和重组数据包。现在,操作系统不再需要处理大量的小数据包,而是可以处理少量的大数据包,这更高效(例如,需要处理的头部更少)。

img

对于分段,在连接平滑性和 CPU 效率之间存在权衡。如果应用程序将大数据包交给 NIC,CPU 的工作量会减少。然而,NIC 现在会收到大量突发数据,连接会更具突发性。相比之下,如果应用程序将小数据包交给 NIC,CPU 的工作量会增加,但 NIC 会收到更稳定的数据流,由此产生的连接会更平滑。

聚合小数据包存在一些挑战。如果中间的某个数据包丢失了怎么办?那么 NIC 可能不得不向上传递一堆小数据包,而无法将它们组合成一个大数据包。如果有些数据包设置了某个标志(如拥塞的 ECN),而其他数据包没有设置,该怎么办?由此产生的聚合数据包应该设置该标志还是不设置?

我们要介绍的第三种可卸载的无状态操作是多队列支持。在我们的标准模型中,NIC 有一个用于传出数据包的队列和一个用于传入数据包的队列,所有应用程序共享这些队列。网络驱动程序(在软件中)负责负载均衡,以防多个应用程序或多个 CPU 在发送和接收数据。

我们可以将这种负载均衡工作卸载到 NIC。现在,NIC 有多个发送队列和多个接收队列。例如,在多处理器系统中,每个 CPU 可以有自己专用的发送 / 接收队列。NIC 并行维护所有队列,确保不同 CPU 之间的隔离和负载均衡。NIC 还可以优先处理某些队列。

尽管 NIC 有多个队列,但它最终仍然必须通过一根线发送所有数据包。因此,NIC 需要一些数据包调度器来决定下一个从哪个队列发送。调度器可以被编程以实现所需的负载均衡行为(例如,如果我们想优先处理某个队列)。

img

多队列的一个挑战是将数据包映射到队列。当 CPU 有数据要发送时,它应该使用哪个队列?特别是,我们希望确保单个流中的所有数据包都进入同一个队列(而不是分散到多个队列中)。这有助于我们确保流中的数据包按顺序发送。回想一下,在 TCP 中,乱序发送数据包是可行的,但对性能不利(例如,接收方必须缓冲乱序数据包)。

在处理来自各个接收队列的传入数据包时,NIC 可以对数据包进行哈希处理,以决定由哪个 CPU 处理该传入数据包。然后,NIC 中断该 CPU,告知其处理该数据包。这种基于哈希的行为类似于 ECMP(等成本多路径路由),有助于确保同一流中的所有数据包由同一个 CPU 按顺序处理。

卸载的简要历史:第 2 时代

后来,我们开始将更复杂的有状态操作卸载到 NIC。

第 2 时代的发展是由数据中心的虚拟化推动的,在数据中心中,多个虚拟机在同一台物理服务器上运行。例如,在虚拟化中,我们需要一个虚拟交换机将传入的数据包转发到相应的虚拟机。我们展示过在软件中运行的虚拟交换机,但虚拟交换机也可以在硬件中实现。

防火墙和带宽管理是有状态卸载的另一个例子。在软件中,我们可以实现一个防火墙来执行安全策略(如丢弃来自这个恶意 IP 的所有传入数据包)。我们还可以执行策略来管理用户之间的带宽(如用户 A 每分钟只能发送 100 个数据包,超出部分将被丢弃)。这些安全策略可以由硬件来检查。

为实现这些有状态操作,我们可以使用类似于 OpenFlow 表(来自 SDN 部分)的匹配 - 动作对表。这个 API 允许软件在硬件上编程不同的策略,以便硬件可以根据这些策略处理数据包。如前所述,匹配可以针对 5 元组或其他一些头部字段。动作可以是丢弃数据包、将数据包转发到特定的下一跳或修改头部。

img

卸载的简要历史:第 3 时代

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

img

理想情况下,我们希望让应用程序直接将数据交给硬件,让硬件在第 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 文件的每个数据包。

img

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

img

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

img

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

img

请注意,WQE 抽象为 RDMA 协议提供了对应用程序的更高层次的视图。在 TCP/IP 协议栈中,网络只看到字节流,但在 RDMA 中,WQE 允许应用程序更详细地描述任务(例如,指定正在传输的数据块的开始和结束)。

当一个任务完成时,WQE 从队列中移除,NIC 创建一个新的结构体,称为完成队列元素(CQE),描述任务的执行情况(如成功或失败)。这个 CQE 存储在完成队列中,等待应用程序准备好读取 CQE 并了解任务的执行情况。

img

请注意,RDMA 是异步的。应用程序可以随时将任务(WQE)添加到队列对中,NIC 将按顺序处理这些任务。同样,当任务完成时,CQE 被放置在完成队列中,应用程序可以随时读取 CQE。(与此形成对比的是 TCP/IP 协议栈,在该协议栈中,传入的数据会触发中断,让 CPU 处理该数据。)

RDMA 示例

RDMA 可用于服务器之间的几种不同操作。每种操作都有其自身的性能规格(如不同的延迟)和不同的语义(如不同的错误消息)。例如,让我们看看 RDMA 发送操作,其中服务器 A 从其内存中读取一个文件,传输该数据,服务器 B 将该文件写入其内存。

  1. 每个服务器指定其内存的某个部分可供 NIC 用于 RDMA 传输。服务器 A 将与文件对应的内存指定为 NIC 可读取。服务器 B 将一个空白缓冲区(用于接收文件)指定为 NIC 可写入。

img

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

img

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

img

img

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

img

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

img

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

img

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 生成)