为什么你的香港服务器吞吐量瓶颈在网卡中断队列?如何用RPS/RFS优化?

这不是一篇理论文章,而是一段我在真实生产环境中,和吞吐量瓶颈反复拉扯的血泪史。
事情发生在去年,我负责的边缘服务在香港IDC上线,面对的是亚太区域爆炸式增长的流量压力。带宽够,CPU富裕,连接池调优完美,但压力测试始终打不满链路速率,PPS明显低于预期。我以为是应用有瓶颈,疯狂优化业务代码和内核参数,直到某天,我无意中 ethtool -S 看了一眼,才发现——是网卡中断队列满了。
这引出了一个很容易被忽略的问题:你的吞吐量瓶颈,可能不在应用,也不在带宽,而是在中断队列的负载均衡上。
本文就从这个问题入手,结合实战,讲清楚中断瓶颈的根源、RPS/RFS 的优化原理,以及如何一步步落地优化。
一、问题现象分析:中断队列瓶颈从哪里来?
1.1 系统症状
以下是我们在香港边缘节点某台物理机上抓到的典型表现:
- iftop 显示带宽利用率远未打满(10G链路实际仅跑了2~3Gbps)。
- top 中 CPU idle 很高,sys 也不高,但某几个 core 100%。
- nstat, ethtool -S eth0 发现某个中断队列的 rx_queue_0 pps 异常高,其他队列几乎空闲。
- mpstat -P ALL 显示 CPU 负载极度不均衡。
看似资源充足,实则某一个中断队列成为瓶颈,拖住了整个包处理流程。
1.2 原因追溯
网卡收到数据包后,会通过中断通知 CPU 处理。多队列网卡(如 Intel 82599、Mellanox 等)支持把不同的包分发到多个队列,以实现并发。但这个分发逻辑默认依赖**RSS(Receive Side Scaling)**算法,通常是基于五元组哈希来分配队列。
问题来了:
- 如果你是服务端,大量连接来自同一客户端 IP,甚至同一端口,哈希结果高度集中。
- 默认中断处理被绑在某个 core 上,其他 core 虽然空闲,但无事可做。
- 最终这个 core 被打爆,产生瓶颈,而其他 core 虚耗资源。
二、什么是 RPS/RFS,它能解决什么问题?
2.1 RPS(Receive Packet Steering)
RPS 是 Linux 内核提供的软中断负载均衡机制。和 RSS 不同,RPS 不是由硬件决定哪个队列收包,而是由内核将数据包从一个 CPU 中断软中断上下文中,转发到其他 CPU 的软中断处理队列中,从而实现跨 CPU 的分流处理。
RPS 的本质是:CPU 之间做包处理的负载均衡,缓解某一个 CPU 的中断处理压力。
2.2 RFS(Receive Flow Steering)
RFS 是在 RPS 基础上的进一步优化:它会跟踪 socket 到 CPU 的映射关系,将包尽可能转发到运行该 socket 应用进程的 CPU 上。
这样做的好处是:
- 降低 cache miss,提升 L2/L3 命中率;
- 提高应用延迟稳定性和整体吞吐。
简言之,RPS 做的是“平衡”,RFS 做的是“亲和”。
三、如何用 RPS/RFS 优化中断瓶颈?实战配置流程
3.1 步骤一:确认硬件与内核支持
uname -r # 确保内核 >= 2.6.35,推荐使用主流 LTS 内核
lscpu # 查看 CPU 核数和 NUMA 架构
lspci | grep -i eth
ethtool -l eth0 # 查看网卡队列数
3.2 步骤二:启用 RPS
为每个网卡接收队列配置 CPU mask(/sys/class/net/eth0/queues/rx-*/rps_cpus)
# 示例:如果你有 8 个 core,要使用 core 0-7,全开为 0xff
echo f > /sys/class/net/eth0/queues/rx-0/rps_cpus
echo f > /sys/class/net/eth0/queues/rx-1/rps_cpus
# 用脚本批量写入
配置 RPS flow table size(/proc/sys/net/core/rps_sock_flow_entries)
echo 32768 > /proc/sys/net/core/rps_sock_flow_entries
每个队列绑定 flow_entries:
for q in /sys/class/net/eth0/queues/rx-*; do
echo 4096 > $q/rps_flow_cnt
done
⚠️ rps_cpus 选择应考虑 NUMA 拓扑,避免跨 NUMA 节点访问。
3.3 步骤三:启用 RFS
RFS 默认开启,确保以下两个参数配置合理:
cat /proc/sys/net/core/rps_sock_flow_entries # 总表项
cat /sys/class/net/eth0/queues/rx-0/rps_flow_cnt # 每个队列分配的 flow entry
确保总数 >= 每个队列总和。
3.4 步骤四:调整中断亲和性(可选但强烈建议)
可使用 irqbalance 关闭,再手动绑核优化:
# 查找 eth0 的中断号
grep eth0 /proc/interrupts
# 绑定中断队列到合适的 core
echo 1 > /proc/irq/123/smp_affinity # 将中断绑定 core0
合理规划 IRQ affinity 和 RPS,一主一辅,避免两者打架。
3.5 验证优化效果
使用以下工具观测优化效果:
- nstat -z:查看 cpu_migrations, tcp_in_segs 等指标是否改善;
- top, htop, mpstat:CPU 核心负载是否均衡;
- ethtool -S:队列分布是否更均匀;
- ping 与 wrk:延迟稳定性是否变好。
四、最终优化成果
在完成上述优化后,我的香港节点吞吐量提升了 将近 60%,PPS 提升显著,网络抖动和 CPU load 明显下降。更重要的是,系统整体更稳定,即使面对突发大流量,也不再“单核爆满、其他空闲”的尴尬局面。
在面对吞吐瓶颈时,不要只盯着应用或带宽。有时候,真正的瓶颈藏在一个小小的中断队列里。RPS/RFS 是 Linux 网络栈中被低估的利器,尤其在中高端服务器、多队列网卡的架构中,它们能带来非常明显的吞吐提升。
愿你读完这篇文章之后,能像我一样,从容应对流量洪峰,不再被中断卡脖子。