WEBKT

既然网卡已经开启了多队列(RSS),为什么依然需要配置 RPS?

2 0 0 0

在 Linux 高性能网络调优的领域中,RSS(Receive Side Scaling,网卡多队列)RPS(Receive Packet Steering,接收数据包引导) 是两个经常被提及的词汇。

很多运维和内核调优新手会有这样一个直观的理解:
> “既然我的物理网卡已经支持多队列(RSS),可以通过硬件哈希把不同连接的流量均匀分发到多个 CPU 核心的硬件中断上,为什么我还需要在软件层面配置 RPS 呢?这难道不是多此一举,反而增加 CPU 的二次调度开销吗?”

这个疑问非常合理。但在实际的大流量生产环境中,仅仅依靠 RSS 往往无法完全榨干多核服务器的性能。本文将从硬件限制、内核中断架构、缓存局部性以及虚拟化场景等维度,深度解析为什么在 RSS 开启的情况下,RPS 依然是不可或缺的优化利器。


一、 核心矛盾:硬件队列数与 CPU 核心数的“不对等”

RSS 的分发能力受限于网卡硬件队列的数量

在当今的服务器配置中,一台双路服务器拥有 64 核、128 核甚至更多物理核心已经非常普遍。然而,许多主流网卡(尤其是千兆网卡、部分万兆网卡,或者虚拟机中的虚拟网卡 VirtIO)其硬件队列数是有限的(例如 4 队列、8 队列或 16 队列)。

  • 没有 RPS 的情况:如果你的服务器有 64 个 CPU 核心,但网卡只有 8 个队列,那么硬件中断最多只能绑定到 8 个 CPU 核心上。这意味着,剩下的 56 个核心完全无法直接参与网卡软中断(softirq)的协议栈收包工作。这 8 个被绑定的核心极易因软中断占满(%si 达到 100%)而丢包,而其他 56 个核心却在“围观”。
  • 引入 RPS 之后:RPS 是 Linux 内核在软件层面的实现。它可以在这 8 个接收到硬件中断的 CPU 核心上,通过软件哈希(通常是 4 元组),将数据包快速分发到剩余的 56 个 CPU 核心上进行后续的协议栈处理(如 IP、TCP/UDP 解析)。

通过 RPS,你成功地将**“8 核工作、56 核围观”的尴尬局面,优化成了“全核协同抗压”**。


二、 算力剥离:硬中断(Hard IRQ)与软中断(Soft IRQ)的解耦

Linux 处理网络数据包分为两个阶段:

  1. 硬中断阶段:网卡收到包,触发 CPU 硬中断,内核快速响应,将数据包从网卡 DMA 环形缓冲区(Ring Buffer)移动到内存,然后挂起软中断。
  2. 软中断阶段(NAPI poll):执行具体的网络协议栈解析(IP 校验、TCP 状态机、寻找 Socket、数据拷贝到用户态)。这一步是CPU 密集型的。

在单纯使用 RSS 的情况下,硬中断在哪个 CPU 核心上触发,对应的软中断(softirq)默认就在同一个 CPU 核心上运行

如果单条队列的并发流量极大,该核心不仅要处理频繁的硬件上下文切换,还要承担沉重的 TCP/IP 协议栈计算。这种“硬+软”绑死在单核上的模式,很容易导致该核心的 ksoftirqd 进程被打爆。

RPS 的介入实现了硬/软中断的解耦

  • 物理网卡的硬中断依然由 RSS 分发到指定的几个核心。
  • 一旦硬中断完成,RPS 在软件层将数据包塞入目标 CPU 的接收队列(input_pkt_queue),并向目标 CPU 发送一个处理器间中断(IPI),指引目标 CPU 去执行软中断。

这样,消耗极小、响应要求极高的硬中断留给少数核心,而消耗极大、适合并行的软中断协议栈处理则被均匀分摊到整个 CPU 簇


三、 缓存局部性与 RFS(Receive Flow Steering)的黄金搭档

提到 RPS,就不得不提它的进阶版 RFS(Receive Flow Steering)

RSS 和 RPS 都是基于数据包的五元组/四元组哈希来进行分发的。它们只管“均匀分发”,却无法感知处理该连接的用户态应用程序(如 Nginx、Redis)当前运行在哪个 CPU 核心上

  • 痛点:如果 RSS/RPS 将数据包分发到了 CPU-A 进行协议栈解析,但处理该连接的 Web 服务线程其实运行在 CPU-B 上。数据包解析完后,还需要跨 CPU 传输到 CPU-B,这会导致大量的 L1/L2 缓存失效(Cache Miss) 和跨 NUMA 内存访问,严重抖动延时。
  • 解决方案:RFS 必须依赖 RPS 框架才能工作。RFS 会在内核中维护一个全局的流表,记录每个连接(Flow)最近被哪个 CPU 上的应用程序读取。当新数据包到达时,RFS 会指示 RPS 将该数据包分发到应用程序所在的 CPU 上进行软中断处理。

如果没有配置 RPS 建立起的软件分发通道,RFS 这套高雅的“应用层-内核层缓存一致性”优化机制将根本无从谈起。


四、 虚拟化环境(KVM / 容器)下的特殊优势

在云计算和虚拟化场景中,虚拟机的网卡(如 virtio-net)虽然也可以通过多队列技术(Multi-queue VirtIO)支持类似 RSS 的功能,但其底层性能受制于宿主机的调度。

  1. vCPU 漂移:虚拟机的 vCPU 可能会被宿主机动态调度到不同的物理 CPU 上。硬件 RSS 的中断亲和性绑定在虚拟机内部可能会失效或滞后。
  2. 多租户干扰:在容器(Pod)环境中,多个容器可能共享同一个物理网卡的多队列。仅靠硬件 RSS 无法细粒度地按容器的 CPU 绑定(Cgroup cpuset)来分发流量。

通过在虚拟机或容器命名空间内配置 RPS,系统管理员可以完全不依赖物理硬件的支持,在纯软件层灵活地控制哪些 CPU 核心参与收包解析。这种高度的灵活性和确定性是硬件 RSS 无法比拟的。


五、 实战:如何判断我是否需要开启 RPS?

在生产环境中,你可以通过以下步骤来诊断是否需要配置 RPS。

1. 查看当前网卡队列与 CPU 核心分布

# 查看网卡队列数
ethtool -l eth0

如果看到 Combined: 8,而你的 CPU 核心数是 32,说明有 24 个核心无法被 RSS 直接覆盖,此时强烈建议开启 RPS

2. 监控软中断 CPU 占用

使用 topmpstat 观察网络繁忙时的 CPU 占用:

watch -d -n 1 "cat /proc/softirqs | grep NET_RX"

或者查看 top 中的 %si(Softirq)占比。如果发现只有某几个 CPU 核心(通常是与网卡中断绑定的核心)的 %si 极高(接近 100%),而其他核心闲得发慌,说明软中断严重分配不均。

3. 如何配置 RPS?

RPS 的配置是通过控制 sysfs 下的掩码实现的。例如,要让 eth0 的第 0 号接收队列(rx-0)将流量分发到 CPU 0-7 上:

# CPU 0-7 的二进制表示为 11111111,转换为十六进制即为 ff
echo "ff" > /sys/class/net/eth0/queues/rx-0/rps_cpus

注:对于多队列网卡,应当为每个 rx-N 队列配置不包含其自身硬中断 CPU 的其他核心掩码,从而实现真正的算力卸载。


总结

RSS 和 RPS 并不是非此即彼的排他关系,而是相辅相成的上下游协作关系

特性 RSS (Receive Side Scaling) RPS (Receive Packet Steering)
层级 硬件(网卡 ASIC 芯片) 软件(Linux 内核网络栈)
操作对象 硬件中断(Hard IRQ) 软件中断(Soft IRQ)
规模限制 受网卡硬件规格限制,队列数通常较少 无限制,可映射到服务器所有 CPU 核心
上下文 将流量分发到不同硬中断线 跨 CPU 投递数据包,触发 IPI 软中断

在高性能网络架构中,最佳的实践往往是:开启 RSS,将硬件中断均匀绑定到一部分 CPU 核心上;同时开启 RPS/RFS,将更沉重的协议栈软中断计算,平摊到所有活跃的 CPU 核心(特别是业务进程所在的 CPU)上。 两者结合,才能真正实现高吞吐、低延迟的网络抗压能力。

KernelSRE Linux 内核网络调优RSS 与 RPS

评论点评