4. Ring Buffer与中断优化:调整RX/TX Ring大小,以及RSS、RPS、RFS、XPS队列分发策略
各位好,咱们今天聊点硬核的。Ring Buffer和中断优化,说白了就是网卡和内核之间怎么「传话」、怎么「递纸条」的问题。我调过不少Intel的服务器,从E5-2600系列到现在的第四代至强,这块要是没调好,CPU再快也是白搭。
你想想看,网卡收进来一个包,得先放到一个地方,然后通知CPU来拿。这个地方就是Ring Buffer。通知的方式就是中断。但这里面坑很多——Ring太小了丢包,太大了浪费内存;中断太频繁CPU扛不住,太少了延迟又高。嗯,咱们一个一个说。
4.1 RX/TX Ring大小:到底设多大合适?
Ring Buffer,其实就是网卡和驱动共享的一块环形内存区域。RX Ring放收进来的包,TX Ring放要发出去的包。每个条目叫一个Descriptor,指向一个sk_buff(内核里的网络缓冲区)。
我个人习惯,先看默认值。Intel的ixgbe驱动,默认RX Ring是512个Descriptor,TX也是512。对于大部分场景,这个值偏保守了。
核心原则:Ring越大,抗突发能力越强,但延迟会稍微变高。Ring越小,延迟低,但容易丢包。
怎么查当前大小?用ethtool:
# 查看当前Ring参数
ethtool -g eth0
# 输出示例
Ring parameters for eth0:
Pre-set maximums:
RX: 4096
RX Mini: 0
RX Jumbo: 0
TX: 4096
Current hardware settings:
RX: 512
RX Mini: 0
RX Jumbo: 0
TX: 512
看到没?硬件最大支持4096,但当前只用了512。我在项目中遇到过,某次线上业务高峰期,网卡疯狂丢包,一看Ring Buffer计数器:
ethtool -S eth0 | grep -i drop
rx_dropped: 87234
rx_missed: 15678
rx_missed这个值,说明硬件层面就已经丢包了——Ring满了,新来的包没地方放。我当时把RX从512调到2048,丢包直接归零。
调整命令很简单:
ethtool -G eth0 rx 2048 tx 2048
我的建议:对于10Gbps以上的网卡,RX Ring至少设到2048。如果是25G/40G,直接上4096。别怕浪费内存,一个Descriptor才占16字节,4096个也就64KB,不值一提。
但要注意,Ring太大也有副作用。我记得有一次调完Ring,发现CPU的cache miss率上去了。为什么?因为Ring里的Descriptor太多,CPU遍历起来更费劲。所以别盲目往大了调,够用就行。
4.2 中断合并:别让CPU被频繁打断
网卡每收一个包就发一次中断,那CPU啥也别干了,光处理中断就忙不过来。所以Intel网卡支持中断合并(Interrupt Coalescing),攒一批包再通知CPU。
关键参数有两个:
- rx-usecs:最多等多少微秒再发中断
- rx-frames:最多攒多少个包再发中断
查看当前设置:
ethtool -c eth0
# 输出示例
Coalesce parameters for eth0:
Adaptive RX: on TX: on
stats-block-usecs: 0
sample-interval: 0
pkt-rate-low: 0
pkt-rate-high: 0
rx-usecs: 3
rx-frames: 0
rx-usecs-irq: 0
rx-frames-irq: 0
tx-usecs: 3
tx-frames: 0
tx-usecs-irq: 0
tx-frames-irq: 0
这里有个「Adaptive RX: on」,意思是网卡自己动态调整中断频率。Intel的网卡这个功能做得不错,我一般建议开着。但如果你的业务对延迟极其敏感(比如高频交易),那就得关掉自适应,手动设一个很小的值。
# 关闭自适应,手动设置低延迟
ethtool -C eth0 adaptive-rx off adaptive-tx off
ethtool -C eth0 rx-usecs 1 rx-frames 1
注意:把rx-usecs设成1,意味着最多等1微秒就发中断。这会让CPU中断频率飙升。我曾经在一台机器上这么干过,结果软中断(softirq)占用了40%的CPU。你得权衡延迟和吞吐量。
4.3 RSS:让多个CPU一起干活
RSS(Receive Side Scaling),说白了就是让网卡把不同流的包,分发到不同的CPU核心上去处理。这样就不会只有一个CPU累死,其他核心在旁边看热闹。
RSS的核心是哈希算法。网卡根据IP、端口等信息算出一个哈希值,然后映射到某个RX队列上。每个RX队列绑定一个CPU核心。
查看当前RSS配置:
# 查看队列数
ethtool -l eth0
# 输出示例
Channel parameters for eth0:
Pre-set maximums:
RX: 0
TX: 0
Other: 0
Combined: 8
Current hardware settings:
RX: 0
TX: 0
Other: 0
Combined: 4
Combined表示同时支持RX和TX的队列数。这里最大8个,当前用了4个。
调整队列数:
ethtool -L eth0 combined 8
然后看每个队列绑定到哪个CPU:
# 查看RSS的CPU亲和性
cat /proc/irq/$(ethtool -S eth0 | grep rx-0 | head -1 | awk -F: '{print $1}')/smp_affinity_list
嗯,这命令有点绕。更直观的做法是用脚本:
for i in $(ls /sys/class/net/eth0/device/msi_irqs/); do
echo -n "IRQ $i -> "
cat /proc/irq/$i/smp_affinity_list
done
我在项目中遇到过一个问题:8个队列全开了,但所有中断都落在CPU0上。为什么?因为BIOS默认没开NUMA均衡。后来我在grub里加了irqbalance服务,或者手动设置smp_affinity才搞定。
经验之谈:RSS队列数最好等于物理CPU核心数,而不是超线程数。比如一台机器有16核32线程,我一般设16个队列。设太多反而因为缓存竞争导致性能下降。
4.4 RPS/RFS:软件层面的RSS
如果网卡不支持RSS,或者队列数不够用,怎么办?别急,内核提供了软件方案——RPS(Receive Packet Steering)和RFS(Receive Flow Steering)。
RPS:把收到的包,从硬中断所在的CPU,转发到另一个CPU上去处理。说白了就是软件层面的负载均衡。
开启RPS很简单,写一个位掩码到/sys/class/net/eth0/queues/rx-*/rps_cpus:
# 让CPU0-3处理RX队列0的包
echo "f" > /sys/class/net/eth0/queues/rx-0/rps_cpus
这里的"f"是十六进制,表示bit 0-3,也就是CPU 0-3。
RFS:比RPS更进一步。它不仅做负载均衡,还考虑CPU缓存局部性——尽量让处理某个流的CPU,和这个流上次被处理的CPU是同一个。这样CPU cache命中率更高。
开启RFS需要设置rps_flow_cnt:
# 每个队列支持4096个流
echo 4096 > /sys/class/net/eth0/queues/rx-0/rps_flow_cnt
# 全局流表大小
echo 32768 > /proc/sys/net/core/rps_sock_flow_entries
我的建议:如果网卡支持RSS,优先用硬件RSS。硬件做哈希比软件快得多。只有网卡队列数不够,或者需要更细粒度的流分发时,才考虑RPS/RFS。
4.5 XPS:发送端的队列分发
XPS(Transmit Packet Steering)和RPS对应,但管的是发送方向。它让每个CPU核心使用特定的TX队列,避免多个CPU竞争同一个TX队列的锁。
开启XPS:
# 让CPU0-3使用TX队列0
echo "f" > /sys/class/net/eth0/queues/tx-0/xps_cpus
嗯,和RPS的配置方式一模一样。但要注意,XPS需要和RSS配合好。一般来说,RX队列i和TX队列i绑定到同一组CPU上,这样收发包都在同一个CPU上完成,cache利用率最高。
4.6 实战:一个完整的调优案例
最后,我分享一个真实的调优过程。某次线上业务,Intel X710网卡,25Gbps链路,跑的是Nginx反向代理。症状是:流量一上来,CPU软中断飙到60%,但吞吐量上不去。
排查步骤:
- 查Ring Buffer:默认512,太小。调到4096。
- 查中断合并:Adaptive开着,但rx-usecs是3。改成1,降低延迟。
- 查RSS队列:只有4个队列,但CPU有16核。调到8个队列,每个队列绑定2个CPU。
- 查XPS:没开。开启XPS,让每个CPU用独立的TX队列。
调完之后,软中断降到20%,吞吐量提升了35%。
避坑指南:我曾经在调完RSS后忘了重启irqbalance服务,结果新队列的中断全落在CPU0上,性能反而更差了。记住,改完队列数后,一定要检查中断的CPU亲和性。
好了,Ring Buffer和中断优化这块,核心就是这几板斧。记住一个原则:硬件能干的,别让软件干;软件能干的,别让CPU干。下一章咱们聊聊NAPI和GRO,那是另一个层面的优化思路。