网卡与驱动调优:RSS、多队列、中断亲和性
网卡,说白了就是服务器和外界打交道的「门卫」。
我见过太多团队,CPU 优化到极致,内存延迟压到个位数,结果一上生产,网卡先扛不住了。丢包、中断风暴、单核打满……问题一个接一个。今天咱们就把网卡这块彻底捋清楚。
为什么网卡会成为瓶颈?
传统网卡只有一个队列。所有数据包进来,都往这一个队列里塞。CPU 呢?也只能一个核去处理这些中断。你想想看,万兆网卡每秒能收上千万个包,一个核根本忙不过来。
我在项目中遇到过,某台机器流量一上来,CPU 0 直接 100%,其他核却在旁边看热闹。这就是典型的「中断不均衡」。
RSS:让多个核一起干活
RSS(Receive Side Scaling)就是解决这个问题的。它让网卡把收到的包,根据哈希值分散到多个队列里。每个队列对应一个 CPU 核,大家各管各的,谁也不抢谁的。
启用 RSS 很简单,但要注意哈希算法。我建议用 Toeplitz 哈希,它比 XOR 哈希更均匀,不容易出现「热点队列」。
# 查看当前 RSS 队列数
ethtool -l eth0
# 设置 RSS 队列数为 4
ethtool -L eth0 combined 4
# 查看 RSS 哈希配置
ethtool -x eth0
嗯,这里要注意:队列数不要超过 CPU 核数。我曾经见过有人设了 16 个队列,但机器只有 8 个核,结果反而因为上下文切换变多了,性能不升反降。
多队列:不只是 RSS
RSS 管的是收包。那发包呢?
现代网卡支持多队列发送(Multi-Queue TX)。每个 CPU 核有自己的发送队列,发数据包时不用抢锁。这在多线程场景下特别重要。
| 特性 | RSS(收) | 多队列 TX(发) |
|---|---|---|
| 作用方向 | 入站 | 出站 |
| 实现方式 | 硬件哈希分流 | 每 CPU 独立队列 |
| 关键参数 | 哈希算法、队列数 | 队列深度、TX 描述符 |
| 常见问题 | 哈希不均匀 | 队列锁竞争 |
我个人习惯,在 DPDK 场景下会把 TX 队列数设成和 CPU 核数一样。但在内核协议栈里,设成核数的一半就够了,因为内核本身有锁开销,队列太多反而浪费。
中断亲和性:把中断绑在正确的核上
光有队列还不够。你得告诉系统:这个队列的中断,交给哪个核去处理。
这就是中断亲和性(IRQ Affinity)。
# 查看中断号
cat /proc/interrupts | grep eth0
# 设置中断亲和性(假设中断号 78)
echo 2 > /proc/irq/78/smp_affinity
# 或者用 irqbalance 自动分配
systemctl start irqbalance
我曾经踩过一个坑:irqbalance 默认会把中断均匀分配到所有核上。但如果你有 NUMA 架构,跨 NUMA 访问内存的延迟很高。正确的做法是:把中断绑在同一个 NUMA 节点上的核。
cat /proc/interrupts 看中断分布,如果某个核的中断数明显偏多,说明设置有问题。
实战调优步骤
好了,理论说完了。咱们直接上实战步骤。
- 确认网卡型号和驱动版本 —— 有些老驱动不支持多队列,先升级再说。
- 设置队列数 —— 用 ethtool -L 设成 CPU 核数或核数的一半。
- 配置 RSS 哈希 —— 用 ethtool -X 选择 Toeplitz 哈希,确保四元组(源 IP、目的 IP、源端口、目的端口)都参与哈希。
- 绑定中断亲和性 —— 把中断绑在同一个 NUMA 节点上,避免跨节点访问。
- 验证效果 —— 用 sar -n DEV 看网卡吞吐,用 mpstat -P ALL 看 CPU 使用率。如果所有核都均匀在干活,说明调优成功了。
避坑指南
我曾经在 Mellanox 网卡上遇到过一个问题:RSS 哈希默认只用了 IP 和端口的一部分,导致同一个连接的数据包被分到了不同队列。结果应用层收到乱序包,性能直接腰斩。
解决办法:用 ethtool -U 添加自定义流规则,强制同一个五元组走同一个队列。
# 添加流规则:源 IP 192.168.1.1 的流量走队列 0
ethtool -N eth0 flow-type ip4 src-ip 192.168.1.1 action 0
嗯,这个技巧在金融交易场景下特别有用。订单流必须保序,乱序会导致成交记录错乱。
另一个坑:虚拟机场景下,网卡多队列需要宿主机和虚拟机都支持。我建议用 virtio 网卡,它原生支持多队列,性能比 e1000 好很多。
最后说一句:网卡调优不是一劳永逸的。流量模型变了,哈希策略就得跟着调。我每季度都会重新跑一遍压测,看看队列分布是否均匀。毕竟,网络是活的,调优也得跟着活。