Ubuntu服务器在高并发网络场景下,经常会遇到一个奇怪的现象:CPU资源明明很充裕,但网络吞吐量就是上不去,延迟抖动还特别大。查看系统负载会发现,所有网络中断都集中在某一个CPU核心上,该核心的si软中断占用率飙到100%,而其他核心却在“围观”。这不是硬件性能不够,而是网卡多队列没有正确配置,导致网络处理能力被单核CPU限制住了。解决这个问题的核心思路,就是启用并合理分配网卡的多队列功能,让网络流量均匀分散到多个CPU核心上处理。
确认网卡是否支持多队列动手调优之前,先要搞清楚硬件能力。并不是所有网卡都支持多队列,老旧网卡或者虚拟化环境中的部分虚拟网卡可能只支持单队列。可以通过ethtool命令直接查看网卡的队列配置情况。
ethtool -l eth0
这条命令会输出网卡的最大支持队列数和当前启用的队列数。重点关注“Pre-set maximums”和“Current hardware settings”这两部分。如果显示Combined字段的Maximum值大于1,说明硬件支持多队列。如果Current值只有1,说明当前只启用了单队列,需要手动调整。
对于不支持ethtool查询的虚拟网卡,比如virtio-net设备,可以检查/sys/class/net/目录下的队列信息。
ls /sys/class/net/eth0/queues/
如果看到多个rx-开头的目录和tx-开头的目录,说明多队列已经生效。只有一个rx-0和tx-0的话,就是单队列状态。
调整网卡队列数量确认硬件支持多队列后,接下来就是把队列数量设置到合理值。队列数量不是越多越好,一般设置为CPU核心数最为合适。假设服务器有8个物理核心,就把网卡队列也设置为8个。
ethtool -L eth0 combined 8
这条命令将网卡队列设置为8个,combined参数表示收发队列合并设置。有些网卡支持收发队列独立设置,可以用rx和tx参数分别指定。设置完成后再次执行ethtool -l eth0确认是否生效。如果提示不支持或者设置失败,可能是网卡驱动限制了最大队列数,需要检查驱动版本或者内核参数。
这个设置重启后会丢失,需要写入持久化配置。在Ubuntu系统中,可以创建systemd服务或者修改/etc/network/interfaces文件,在网卡启动时自动执行ethtool命令。更推荐的做法是使用udev规则,在网卡加载时自动应用队列设置。
# 创建udev规则文件 echo 'ACTION=="add", SUBSYSTEM=="net", KERNEL=="eth0", RUN+="/usr/sbin/ethtool -L eth0 combined 8"' > /etc/udev/rules.d/99-nic-queues.rules配置中断亲和性与RPS/RFS
网卡队列数量设置好了,接下来要解决中断分配问题。每个队列会产生对应的硬件中断,这些中断默认可能都绑定到CPU0上,导致单核瓶颈依然存在。需要手动调整中断亲和性,把不同队列的中断分散到不同CPU核心上。
首先查看网卡中断号。
grep eth0 /proc/interrupts
输出会列出每个队列对应的中断号。然后通过设置/proc/irq/中断号/smp_affinity_list来绑定CPU核心。比如把中断号123绑定到CPU核心2上。
echo 2 > /proc/irq/123/smp_affinity_list
手动逐个设置效率太低,推荐使用irqbalance服务自动分配。Ubuntu默认安装了irqbalance,但它的默认策略可能不够激进。可以修改/etc/default/irqbalance文件,添加IRQBALANCE_BANNED_CPUS和IRQBALANCE_ARGS参数来精细化控制。对于需要极致性能的场景,建议关闭irqbalance,手动编写脚本精确绑定中断到指定核心。
如果网卡硬件队列数量少于CPU核心数,或者虚拟化环境无法调整硬件队列,就需要启用软件层面的RPS和RFS来弥补。RPS将数据包处理的软中断分散到多个CPU核心上,RFS则进一步将数据包处理引导到应用所在的CPU核心上,减少缓存失效。
# 启用RPS,将CPU核心0-7都参与报文处理 echo "ff" > /sys/class/net/eth0/queues/rx-0/rps_cpus # 启用RFS,设置流表大小 echo 32768 > /proc/sys/net/core/rps_sock_flow_entries echo 4096 > /sys/class/net/eth0/queues/rx-0/rps_flow_cnt
rps_cpus是一个位掩码,ff表示低8位全为1,也就是CPU0到CPU7都参与处理。如果有16个核心,可以写成ffff。每个接收队列都需要单独设置rps_cpus。RFS的流表大小要根据并发连接数来调整,一般设置为32768就能满足大多数场景。
调整内核网络参数配合多队列网卡多队列配置完成后,还需要调整内核网络参数,让整个网络栈的处理能力匹配上多队列带来的并发处理能力。这些参数直接影响数据包从网卡到应用层之间的处理效率。
首先是数据包处理预算的调整。netdev_budget和netdev_budget_usecs两个参数控制每次软中断处理的数据包数量和时间上限。多队列环境下,每个队列都可能触发软中断,需要适当提高预算。
sysctl -w net.core.netdev_budget=600 sysctl -w net.core.netdev_budget_usecs=8000
默认的netdev_budget只有300,在高吞吐场景下很容易被打满,导致数据包处理延迟增加。提高到600甚至1000,能让单次软中断处理更多数据包。但也不能设置过高,否则会导致软中断占用CPU时间过长,影响用户态进程的执行。
其次是接收队列长度的调整。net.core.netdev_max_backlog决定了数据包进入协议栈之前的等待队列长度。多队列环境下,多个队列同时向协议栈投递数据包,队列长度需要相应增加。
sysctl -w net.core.netdev_max_backlog=5000
默认值1000在万兆网卡环境下明显不够用,调整为5000甚至更高,可以避免突发流量下的丢包。同时也要调整每个套接字的接收缓冲区大小。
sysctl -w net.core.rmem_max=16777216 sysctl -w net.core.wmem_max=16777216 sysctl -w net.ipv4.tcp_rmem="4096 87380 16777216" sysctl -w net.ipv4.tcp_wmem="4096 65536 16777216"
这些参数确保TCP连接有足够的缓冲区来容纳多队列并行处理带来的数据量。tcp_rmem和tcp_wmem的三个值分别表示最小值、默认值和最大值,最大值设置为16MB能够满足大多数高吞吐场景。
NUMA架构下的多队列优化如果服务器是NUMA架构,多队列配置还需要考虑CPU与内存的亲和性。网卡通常连接到特定的NUMA节点上,如果数据包被分配到另一个NUMA节点的CPU上处理,就会产生跨节点内存访问,延迟显著增加。正确的做法是让网卡队列的中断和处理都绑定到同一NUMA节点内的CPU核心上。
首先查看网卡所在的NUMA节点。
cat /sys/class/net/eth0/device/numa_node
输出0或1,表示网卡连接在哪个NUMA节点上。然后查看该NUMA节点包含哪些CPU核心。
cat /sys/devices/system/node/node0/cpulist
假设node0包含CPU0-7,node1包含CPU8-15,而网卡在node0上。那么就应该把网卡中断绑定到CPU0-7上,避免绑定到CPU8-15。RPS的rps_cpus掩码也应该只包含node0的核心。
# 只让node0的CPU0-7参与报文处理 echo "ff" > /sys/class/net/eth0/queues/rx-0/rps_cpus
如果应用本身也绑定在node0上运行,整个数据路径就都在同一NUMA节点内,延迟最低。对于多网卡的服务器,每个网卡都应该遵循同样的原则,就近绑定到所在NUMA节点的CPU核心上。
验证多队列调优效果所有配置完成后,需要验证多队列是否真正生效,以及性能是否达到预期。最直观的方法是观察中断分布情况。
watch -n1 cat /proc/interrupts | grep eth0
如果看到各个队列的中断计数都在均匀增长,说明中断分配正确。如果某个队列的中断计数明显偏高,说明该队列的负载不均衡,需要检查中断绑定配置。
进一步查看每个CPU核心的软中断处理情况。
mpstat -P ALL 1
关注%soft列,如果多个核心的软中断占用率都比较均衡,说明多队列调优成功。如果只有单个核心的软中断很高,说明还有优化空间。
还可以使用sar命令查看网络设备的统计信息。
sar -n DEV 1
观察rxpck/s和txpck/s的数值,结合带宽利用率来判断吞吐量是否达到预期。对于万兆网卡,单队列通常只能跑到3-5Gbps左右,启用多队列后应该能接近线速。
最后用netstat或者ss命令查看TCP连接的分布情况,确认RFS是否将连接正确引导到了应用所在的CPU核心上。
ss -ti state established
输出的连接信息中可以看到每个连接的处理CPU,如果大部分连接都集中在少数核心上,说明RFS配置可能需要调整。
常见问题排查多队列调优过程中经常会遇到一些棘手问题。最常见的是ethtool设置队列数量时报错“Cannot set device channel parameters: Invalid argument”。这通常是因为网卡驱动不支持动态调整队列数量,需要重新加载驱动模块并传入参数。对于ixgbe驱动的Intel万兆网卡,可以在/etc/modprobe.d/ixgbe.conf中添加参数。
options ixgbe RSS=8,8,8,8
这表示为每个网口设置8个RSS队列。修改后需要重新加载模块才能生效。
另一个常见问题是irqbalance服务与手动绑定的冲突。irqbalance会定期重新分配中断,可能覆盖手动设置的smp_affinity。如果选择手动绑定,必须关闭irqbalance。
systemctl stop irqbalance systemctl disable irqbalance
虚拟机环境中,virtio网卡的多队列支持需要宿主机和客户机同时配置。客户机中的队列数量不能超过宿主机分配给虚拟机的vCPU数量。如果客户机有4个vCPU,但网卡队列设置了8个,多余的队列不会生效。
XDP和DPDK等高性能网络框架也会影响多队列配置。这些框架绕过了内核网络栈,直接操作网卡队列,需要在其配置文件中单独指定队列数量和CPU绑定策略,与内核层面的配置互不影响。
持续监控与动态调整多队列调优不是一劳永逸的事情。服务器的工作负载会变化,网络流量模式也会变化。建议部署监控系统,持续跟踪每个CPU核心的软中断占用率、网卡队列的中断计数、以及网络吞吐量和延迟指标。当发现负载不均衡时,及时调整中断绑定和RPS配置。
可以编写简单的监控脚本,定期检查中断分布情况,当某个核心的软中断超过阈值时自动告警。
#!/bin/bash
# 检查CPU软中断均衡情况
mpstat -P ALL 1 1 | awk '/Average/ && /all/ {next} /Average/ {print $3, $NF}' | \
awk '{if($2>50) print "WARNING: CPU "$1" softirq "$2"%"}'
对于流量变化剧烈的场景,可以考虑使用自适应RPS配置。根据当前的流量分布动态调整rps_cpus掩码,让更多或更少的CPU核心参与报文处理。这需要结合业务特点进行定制开发。
Ubuntu网卡多队列调优的本质,是把网络处理能力从单核瓶颈中解放出来,充分利用多核CPU的并行处理能力。从硬件队列的启用,到中断亲和性的绑定,再到内核网络参数的配合,每一步都需要精确配置。调优完成后,服务器的网络吞吐量通常能提升数倍,延迟抖动也会大幅降低,真正发挥出硬件的全部性能。
