突破单核软中断瓶颈:云服务器环境下通过 RPS/RFS 解决 Nginx 丢包实战
在公有云环境(如阿里云、腾讯云、AWS 等)中部署高并发、大吞吐量的 Nginx 网关时,你可能会遇到这样一种诡异的现象:系统整体 CPU 利用率并不高(甚至低于 30%),但 Nginx 开始出现随机的连接超时、握手失败或响应丢包;通过 top 观察,发现 CPU0(或某单个核心)的 si(软中断)占比长期处于 90% ~ 100% 满载状态,而其他 CPU 核心却闲得发慌。
这种由于单核网络软中断过高导致的性能瓶颈,在云服务器(尤其是规格较小、不支持硬件多队列网卡,或者多队列未与 vCPU 绑定均衡的 VM)上极为常见。
本文将深入探讨该问题的底层根源,并提供通过调节 Linux 内核 RPS (Receive Packet Steering) 和 RFS (Receive Flow Steering) 参数,将单核软中断负载完美分摊到多核上的完整实战指南。
一、 为什么会发生单核软中断过高?
在传统的物理服务器中,网卡通过 RSS (Receive Side Scaling, 接收端缩放) 硬件功能,利用哈希算法将不同的网络流分发到不同的硬件接收队列(Rx Queue)中,每个队列对应一个独立的 CPU 中断。
但在**云服务器(虚拟机)**环境下,情况会有所不同:
- 网卡队列受限:低规格的云服务器通常只分配了单队列(Single Queue)虚拟网卡。
- 中断绑定不均:即使云服务器支持多队列,虚拟化层(Hypervisor)分发中断的机制也可能失效,或者网卡中断默认全部绑定到了
CPU0上。
当大流量涌入时,所有的网卡接收中断(NET_RX)全部由 CPU0 独占处理。一旦 CPU0 的软中断处理能力达到饱和,后续到达的报文就会在内核的接收队列(backlog)中积压,最终被无情丢弃。此时,跑在其他核心上的 Nginx 工作进程由于拿不到报文,只能处于饥饿状态,导致客户端感知到丢包和高延迟。
二、 核心武器:RPS 与 RFS
为了解决硬件/虚拟化层无法多队列分流的问题,Linux 内核在 2.6.35 版本引入了软件模拟的解决方案:
RPS (Receive Packet Steering):
- 原理:在软件层面模拟多队列网卡。当单个 CPU 核心(如 CPU0)收到网卡中断并把报文从 ring buffer 读入内核后,在软件中断上下文里,根据报文的四元组(源IP、目的IP、源端口、目的端口)计算出哈希值,然后将报文分发到指定的其他 CPU 核心的接收队列中,由这些 CPU 协助处理后续的协议栈解码工作。
- 作用:将
NET_RX的 CPU 消耗分摊到多个核心。
RFS (Receive Flow Steering):
- 原理:RPS 只管哈希分发,不管应用进程在哪运行。这会导致“CPU-A 刚处理完报文协议栈,却要把数据复制给运行在 CPU-B 上的 Nginx 进程”的情况,造成极大的 CPU 缓存缺失(Cache Miss)。RFS 则是 RPS 的智能升级版,它会感知当前应用程序(如 Nginx 进程)运行在哪个 CPU 上,并优先将该连接的网络报文分发到该应用程序所在的 CPU 核心上处理。
- 作用:提升 CPU 缓存命中率,进一步降低网络延迟和系统开销。
三、 故障诊断与指标观测
在动手优化前,必须先确诊。请依次执行以下命令:
1. 确认软中断分布
使用 mpstat(来自 sysstat 工具包)观察各核心的软中断占比:
mpstat -P ALL 1 3
观察 %soft(或 top 下的 %si)列。如果仅有 CPU0 接近 100%,而其他核心接近 0%,则基本确诊。
也可以直接查看系统软中断计数器的变化:
watch -d cat /proc/softirqs
重点观察 NET_RX(网络接收)这一行在各个 CPU 上的增长速度。
2. 确认 Nginx 是否在丢包
查看内核网络丢包计数器:
netstat -s | grep "packet receive errors"
或者查看网卡底层的丢包统计:
ethtool -S eth0 | grep rx_dropped
如果这些计数器在持续飙升,说明网卡收包缓冲区或内核接收队列已经溢出。
3. 检查网卡多队列支持情况
ethtool -l eth0
- 如果
Combined值为1,说明是单队列网卡,必须开启 RPS/RFS 进行软件分流。 - 如果
Combined大于1且当前已启用,但中断依然倾斜,说明中断绑定未生效或需要 RPS 辅助。
四、 RPS 与 RFS 优化配置实战
下面以单网卡 eth0、8核云服务器(CPU 编号 0-7)为例进行配置。
第一步:计算并配置 RPS CPU 掩码(Bitmask)
RPS 通过一个十六进制的 CPU 掩码来控制将流量分发到哪些核心。
掩码计算规则:
每一个 CPU 核心对应二进制中的一位(从右往左,CPU0 是最低位)。
假设我们有 8 核,想让 CPU0-CPU7 全部参与网络收包处理:CPU7 CPU6 CPU5 CPU4 CPU3 CPU2 CPU1 CPU0 1 1 1 1 1 1 1 1 对应的二进制为
11111111,转换为十六进制即为ff。如果你希望避开 CPU0(让 CPU0 只负责物理中断,其他核心负责软中断),则二进制为
11111110,转换为十六进制即为fe。写入 RPS 配置:
将计算好的掩码写入网卡接收队列的控制文件。若为单队列网卡,对应的路径为rx-0:echo "ff" > /sys/class/net/eth0/queues/rx-0/rps_cpus(注:如果是多队列网卡,如
rx-0、rx-1等,需对所有队列分别写入对应的掩码。)
第二步:配置全局和单队列的 RFS 流表大小
启用 RFS 需要配置两个参数:全局套接字流表大小(rps_sock_flow_entries)和单个队列的流表大小(rps_flow_cnt)。
参数大小估算:
rps_sock_flow_entries:建议设置为最大预期并发连接数。在高并发 Nginx 网关中,通常设置为32768或65536。rps_flow_cnt:单个队列分配的流表大小。计算公式:rps_flow_cnt = rps_sock_flow_entries / 接收队列数量。
在我们的单队列(rx-0)示例中:rps_flow_cnt直接设为65536。如果是双队列,则每个设为32768。
写入 RFS 配置:
- 设置全局 RFS 流表:
sysctl -w net.core.rps_sock_flow_entries=65536 - 设置单个队列的 RFS 流表(单队列网卡示例):
echo 65536 > /sys/class/net/eth0/queues/rx-0/rps_flow_cnt
- 设置全局 RFS 流表:
第三步:调整内核网卡接收队列溢出上限
默认的内核网络驱动队列限制较小,高并发下即使分流也容易瞬间溢出,建议同步放大以下内核参数:
编辑 /etc/sysctl.conf 并追加:
# 每个网络接口接收数据包的速率比内核处理这些包的速率快时,允许送到队列的数据包的最大数目
net.core.netdev_max_backlog = 16384
# 允许系统分配的套接字最大缓冲区
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
执行命令使之生效:
sysctl -p
五、 联动优化:Nginx CPU 亲和性(Affinity)
在开启 RFS 后,由于 RFS 会主动追踪 Nginx 工作进程所在的 CPU 核心并将报文定向分发过去,因此锁定 Nginx 进程的 CPU 亲和性能够产生极强的协同效应,最大化发挥 RFS 的就近处理优势。
修改 Nginx 配置文件 /etc/nginx/nginx.conf:
user nginx;
# 自动根据 CPU 核心数启动对应数量的 worker 进程
worker_processes auto;
# 开启自动 CPU 亲和性绑定,将 worker 进程一一绑定到不同的 CPU 核心上
worker_cpu_affinity auto;
events {
# 适当调大单个 worker 的连接限制
worker_connections 65535;
use epoll;
}
保存并重载 Nginx 配置:
nginx -s reload
六、 优化效果验证
完成配置后,使用压测工具(如 wrk 或 ab)重新发起高并发请求,并观察系统指标变化:
软中断均衡度:
再次运行mpstat -P ALL 1,你应当会看到先前一家独大的CPU0的%soft显著下降,而CPU1~CPU7的%soft开始均匀上升。丢包情况:
持续观察netstat -s | grep "packet receive errors"计数器,应当停止增长,Nginx 的连接建立超时报错消失。延迟指标:
在客户端测试请求,P99、P999 的响应延迟将会变得更加平滑,不再出现由于包排队导致的尖峰。
七、 配置持久化脚本
为了防止云服务器重启后上述 /sys 下的临时配置丢失,建议将初始化命令写成脚本,并在系统启动时执行。
创建脚本 /etc/rc.local(或通过 systemd 服务调用):
#!/bin/bash
# 激活 eth0 网卡的 RPS,分发至所有 8 个核心
echo "ff" > /sys/class/net/eth0/queues/rx-0/rps_cpus
# 激活 eth0 网卡的 RFS
echo 65536 > /sys/class/net/eth0/queues/rx-0/rps_flow_cnt
exit 0
确保脚本具有可执行权限:
chmod +x /etc/rc.local
总结
在云原生和虚拟化环境中,受限于物理资源抽象和虚拟网卡能力,单核网络软中断瓶颈是制约高性能网关吞吐量的常见拦路虎。通过启用 RPS 与 RFS,我们在软件层面架设了一座多路分流桥梁,配合 Nginx CPU 绑定,实现了网络报文从驱动层、内核协议栈到应用层进程的“全链路单核就近处理”。该优化不花一分钱,却能极大压榨云服务器的 CPU 潜在网卡处理极限,是每位架构师和 SRE 的必备调优技能。