1.RSS(Receive Side Scaling)
? RSS(Receive Side Scaling)也叫多队列接收,可以硬件级别实现使用多核处理接收网络数据。可用于缓解因单个CPU过载,导致的接收中断处理瓶颈,并减少网络延迟。
? 可以通过/proc/interrupts确定网络接口卡是否支持RSS。也可以在加载网络驱动程序后,通过“ls -1 /sys/devices///device_pci_address/msi_irq” 的输出来查看。(需要网卡硬件及驱动同时支持)
? 默认情况下,RSS是启用的。RSS的队列数量通常在/sys/class/net/device/queues/rx queue/中配置(device是网络设备的名称(例如eth1),rx queue是相应接收队列的名称(如rx-0))。
? 在配置RSS时,RedHat建议将队列数量限制为每个物理CPU核心一个队列。启用RSS后,会根据每个CPU使用情况来分配数据的处理。也可以使用ethtool --show-rxfh-indir和–set-rxfh-indir参数来修改使用的权重。
? irqbalance守护进程可以与RSS一起使用,它将减少跨节点内存传输和缓存线跳转的可能性。如果开启了irqbalance和RSS,通过irqbalance将与网络设备相关的中断定向到适当的RSS队列,可以实现最低的延迟。
2.RPS( Receive Packet Steering)
? RPS与RSS类似,用于将数据包定向到特定的CPU进行处理。RPS是在软件级别实现的,有助于避免单队列网卡成为网络流量的瓶颈。与RSS相比,RPS有几个优势:
? RPS是在/sys/class/net/device/queues/rx-queue/rps_cpus文件中配置接收队列(device是网络设备的名称(如eth0),rx-queue是相应接收队列的名称(如rx-0))。rps_cpus文件默认值为0,也就是禁用RPS,CPU既会处理数据中断,也会处理数据包。要让指定的CPU处理接收队列的中断数据,需要将对应CPU索引位设置为1。如,要使用CPU 0、1、2和3处理中断,只需要将rps_cpus文件值设置为00001111或 f(十六进制值=1+2+4+8)。
? 对于单队列的网络设备,可以通过将RPS配置为在同一内存域中使用CPU,来实现最佳性能。在非NUMA系统上,这意味着可以使用所有可用的CPU。如果网络中断率极高时,非处理网络中断的CPU,也可能会提高性能。
? 对于多队列的网络设备,同时开启RPS和RSS通常是没有用处的。因为默认情况下,RSS会将CPU映射到每个接收队列。注意:如果硬件队列少于CPU,并且RPS被配置为在同一内存域中使用CPU,则RPS仍然是有用的。
? 开启RPS后softirq消耗仍不均衡:发现开启rps后,softirq不均衡的情况有缓解,但仍有较大差异。用perf分析高消耗cpu的ksoftirqd,发现有很大一部分cpu消耗在网卡驱动的清理流程中。rps只能减轻cpu处理网络协议的开销,对于网卡驱动上的开销无优化效果,对于这种情况可以设置rps_cup时,排除本身硬中断处理的cpu,来进一步减轻其开销。
3.RFS(Receive Flow Steering)
? RFS是RPS的功能扩展,用来提高CPU缓存命中率,减少网络延迟。RPS仅根据队列长度转发数据包,而RFS使用RPS后端计算最合适的CPU,然后根据数据包的应用程序的位置来转发数据包。(同1个sockect的包,命中为同1个CPU进行处理)
? 默认情况下禁用RFS。要启用RFS,必须编辑两个文件:
-
/proc/sys/net/core/rps_sock_flow_entries:指定并发活动连接的最大预期数量。对于中等服务器负载,建议使用32768。在实践中,所有输入的值都被四舍五入到最接近的2次方。 -
/sys/class/net/device/queues/rx-queue/rps_flow_cnt:它的值为rps_sock_flow_entries的值除以接收队列的数量。例如,如果rps_flow_条目设置为32768,并且有16个配置的接收队列,则rps_flow_cnt应设置为2048。对于单队列设备,rps_flow_cnt的值与rps_sock_flow_条目的值相同。 ? 同1个sockect的包,命中为同1个CPU进行处理。如果单个应用数据量大于单个CPU可以处理的数据量,可以通过配置更大的帧大小,来减少中断次数,从而减少CPU的处理工作量。或者,考虑 NIC offload选项或更快的CPU。
4.RFS加速
? 增加硬件辅助来提高RFS的速度,从而实现PFS的加速。
? 只有满足以下条件,加速RFS才可用:网卡支持加速RFS;启用ntuple筛选;
? 一旦满足这些条件,就会根据传统的RFS配置自动推导CPU到队列的映射。也就是说,CPU到队列的映射是基于驱动程序为每个接收队列配置的IRQ亲和力推导出来的。
5.NIC OFFLOADS
? 默认的以太网最大传输单元(MTU)是1500字节,这是通常可以传输的最大帧大小。这可能会导致系统资源未充分利用,例如,如果有3200字节的数据用于传输,这将意味着生成三个较小的数据包。有几个称为offloads的选项,允许相关协议栈传输比正常MTU大的数据包。可以创建最大允许64KiB的数据包,并提供发送(Tx)和接收(Rx)选项。当发送或接收大量数据时,这意味着每发送或接收64KiB数据,就要处理一个大数据包,而不是多个较小的数据包。这意味着生成的中断请求更少,用于拆分或组合流量的处理开销更少,传输机会更多,从而导致吞吐量的总体增加。
5.1.Offload类型
TCP Segmentation Offload (TSO)
? 使用TCP协议发送大数据包。使用NIC处理段,然后将TCP、IP和数据链路层协议头添加到每个数据段。
UDP Fragmentation Offload (UFO)
? 使用UDP协议发送大数据包。使用NIC将IP碎片处理为MTU大小的数据包,用于大型UDP数据报文。
Generic Segmentation Offload (GSO)
? 使用TCP或UDP协议发送大数据包。如果NIC无法处理数据段/报文,GSO将绕过NIC硬件执行相同的操作。它通过尽可能晚地延迟分段来实现的。
Large Receive Offload (LRO)
? 使用TCP协议。所有传入数据包在接收时,都会重新分段,从而减少系统必须处理的段数。它们可以在驱动程序中合并,也可以使用NIC合并。
? LRO的一个问题是:它倾向于对所有传入的数据包进行重新排序,通常会忽略标头和其他可能导致错误的信息的差异。当启用IP转发时,通常不可能使用LRO。LRO与IP转发相结合可能会导致校验和错误。注意:如果/proc/sys/net/ipv4/ip_forward设置为1,则启用转发。
Generic Receive Offload (GRO)
? 使用TCP或UDP协议。在对数据包重新排序时,GRO比LRO更严格。例如,它检查每个数据包的MAC报头(必须匹配),只有有限数量的TCP或IP报头可以不同,并且TCP时间戳必须匹配。重新排序可以由NIC或GSO代码处理。
5.2.使用NIC Offloads
? Offload应该用于传输或接收大量数据的高速系统,并且有利于吞吐量而不是延迟。使用Offload大大增加了驱动程序队列的容量,所以延迟可能会成为一个问题。
? 这方面的一个例子,一个系统使用大数据包大小传输大量数据,但也运行许多交互式应用程序。由于交互式应用程序以一定的时间间隔发送小数据包,因此在处理前面较大的数据包时,这些数据包可能会“被困”在缓冲区中,从而造成不可接受的延迟,这是一个非常现实的风险。
? 可以使用ethtool命令检查Offload设置,某些设备可能被列为固定设置,这意味着它们无法更改。
6.XPS(Transmit Packet Steering)
? XPS通过创建CPU到网卡发送队列的对应关系,来保证处理发送软中断请求的CPU和向外发送数据包的CPU是同一个CPU,用来保证发送数据包时候的局部性。
? 对于发送队列到CPU的映射有两种选择:
- 使用CPU映射:通过指定发送队列在某几个CPU上处理,通过减小分发的CPU范围来减少锁开销以及cache miss。最常见的就是1对1,和上面说到的接收软中断绑核类似,通过 /sys/class/net//queues/tx-/xps_cpus文件设置,同样是bitmaps方式。
- 接收队列映射方式:基于接收队列的映射来选择CPU,也就是说让接收队列和发送队列在同一个CPU,或指定范围的几个CPU来处理。这种方式对于多线程一直收发包的系统效果比较明显,收发包队列处理在同一个CPU,不仅减少了对其他CPU的打断,同时提高应用处理效率,收完包后直接在同个CPU继续发包,从而减小CPU消耗,同时减小包的时延。可通过/sys/class/net//queues/tx-/xps_rxqs文件设置(不是所有网卡都支持)。
? XPS对于单发送队列网卡没有效果
7.单网卡调优:开启RPS和RFS(默认关闭)
-
开启CPU性能模式:cpupower frequency-set -g performance -
开启RPS和RFS; -
ethtool -G p1p1 [rx|tx] 4096,检查设置结果ethtool -g p1p1 -
sysctl -w net.core.netdev_budget=600 -
查看网卡相应收发包情况;
开启RPS和RFS的脚本示例:
#!/bin/bash
# cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
# cpupower frequency-set -g performance
# activate rps/rfs by script: https://gist.github.com/wsgzao/18828f69147635f3e38a14690a633daf
# double ring buffer size: ethtool -G p1p1 [rx|tx] 4096, ethtool -g p1p1
# double NAPI poll budget: sysctl -w net.core.netdev_budget=600
rps_start()
{
net_interface=`ip link show | grep "state UP" | awk '{print $2}' | egrep -v '^docker|^veth' | tr ":\n" " "`
for em in ${net_interface[@]}
do
rq_count=`ls /sys/class/net/$em/queues/rx-* -d | wc -l`
rps_flow_cnt_value=`expr 32768 / $rq_count`
for ((i=0; i< $rq_count; i++))
do
echo $rps_flow_cnt_value > /sys/class/net/$em/queues/rx-$i/rps_flow_cnt
done
flag=0
while [ -f /sys/class/net/$em/queues/rx-$flag/rps_cpus ]
do
echo `cat /sys/class/net/$em/queues/rx-$flag/rps_cpus | sed 's/0/f/g' ` > /sys/class/net/$em/queues/rx-$flag/rps_cpus
flag=$(($flag+1))
done
done
echo 32768 > /proc/sys/net/core/rps_sock_flow_entries
sysctl -p
}
rps_stop() {
net_interface=`ip link show | grep "state UP" | awk '{print $2}' | egrep -v '^docker|^veth' | tr ":\n" " "`
for em in ${net_interface[@]}
do
rq_count=`ls /sys/class/net/$em/queues/rx-* -d | wc -l`
rps_flow_cnt_value=`expr 32768 / $rq_count`
for ((i=0; i< $rq_count; i++))
do
echo 0 > /sys/class/net/$em/queues/rx-$i/rps_flow_cnt
done
flag=0
while [ -f /sys/class/net/$em/queues/rx-$flag/rps_cpus ]
do
echo 0 > /sys/class/net/$em/queues/rx-$flag/rps_cpus
flag=$(($flag+1))
done
done
echo 0 > /proc/sys/net/core/rps_sock_flow_entries
sysctl -p
}
rps_status() {
ni_list=`ip link show | grep "state UP" | awk '{print $2}' | egrep -v "^docker|^veth" | tr ":\n" " "`
for n in $ni_list
do
rx_queues=`ls /sys/class/net/$n/queues/ | grep "rx-[0-9]"`
for q in $rx_queues
do
rps_cpus=`cat /sys/class/net/$n/queues/$q/rps_cpus`
rps_flow_cnt=`cat /sys/class/net/$n/queues/$q/rps_flow_cnt`
echo "[$n]" $q "--> rps_cpus =" $rps_cpus ", rps_flow_cnt =" $rps_flow_cnt
done
done
rps_sock_flow_entries=`cat /proc/sys/net/core/rps_sock_flow_entries`
echo "rps_sock_flow_entries =" $rps_sock_flow_entries
}
case "$1" in
start)
echo -n "Starting $DESC: "
rps_start
rps_status
;;
stop)
rps_stop
rps_status
;;
restart|reload|force-reload)
rps_stop
rps_start
;;
status)
rps_status
;;
*)
echo "Usage: $0 [start|stop|status]"
;;
esac
exit 0
**实际测试:**开启RPS后,可以一定程度上解决网卡数据集中在一个CPU核处理的问题,但CPU消耗会明显增加;
8.多队列网卡
8.1.查看主网卡支持多队列的情况
? 其中Pre-set maximums中的combined字段大于1时,表示网卡支持多队列。而Current harware settings中的conbined则表示当前设置的网卡队列数,如果队列数大于1,就是已经开启了网卡多队列。
[root@localhost ~]# ethtool -l eth0
Channel parameters for eth0:
Pre-set maximums:
RX: 0
TX: 0
Other: 0
Combined: 64
Current hardware settings:
RX: 0
TX: 0
Other: 0
Combined: 32
8.2.设置网卡多队列
ethtool -L eth1 combined 16
? 注意:设置的队列数的前提是网卡首先要支持多队列,且不能超过网卡支持的最大的队列数。当网卡驱动比较老旧的时候,也有可能会设置失败,建议将网卡驱动先升级至最新版本。
? Redhat的RSS参考文档
|