作者:牧原
什么是中断?
当一个硬件(如磁盘控制器或者以太网卡), 需要打断CPU的工作时, 它就触发一个中断. 该中断通知CPU发生了某些事情并且CPU应该放下当前的工作去处理这个事情. 为了防止多个设置发送相同的中断, Linux设计了一套中断请求系统, 使得计算机系统中的每个设备被分配了各自的中断号, 以确保它的中断请求的唯一性.
从2.4 内核开始, Linux改进了分配特定中断到指定的处理器(或处理器组)的功能. 这被称为SMP IRQ affinity, 它可以控制系统如何响应各种硬件事件. 允许你限制或者重新分配服务器的工作负载, 从而让服务器更有效的工作.
以网卡中断为例,在没有设置SMP IRQ affinity时, 所有网卡中断都关联到CPU0, 这导致了CPU0负载过高,而无法有效快速的处理网络数据包,导致了瓶颈。
通过SMP IRQ affinity, 把网卡多个中断分配到多个CPU上,可以分散CPU压力,提高数据处理速度。但是smp_affinity要求网卡支持多队列,如果网卡支持多队列则设置才有作用,网卡有多队列,才会有多个中断号,这样就可以把不同的中断号分配到不同CPU上,这样中断号就能相对均匀的分配到不同的CPU上。
而单队列的网卡可以通过RPS/RFS来模拟多队列的情况,但是该效果并不如网卡本身多队列+开启RPSRFS 来的有效
什么是RPS/RFS
RPS(Receive Packet Steering)主要是把软中断的负载均衡到各个cpu,简单来说,是网卡驱动对每个流生成一个hash标识,这个HASH值得计算可以通过四元组来计算(SIP,SPORT,DIP,DPORT),然后由中断处理的地方根据这个hash标识分配到相应的CPU上去,这样就可以比较充分的发挥多核的能力了。通俗点来说就是在软件层面模拟实现硬件的多队列网卡功能,如果网卡本身支持多队列功能的话RPS就不会有任何的作用。该功能主要针对单队列网卡多CPU环境,如网卡支持多队列则可使用SMP irq affinity直接绑定硬中断。
图1 只有RPS的情况下(来源网络)
由于RPS只是单纯把数据包均衡到不同的cpu,这个时候如果应用程序所在的cpu和软中断处理的cpu不是同一个,此时对于cpu cache的影响会很大,那么RFS(Receive flow steering)确保应用程序处理的cpu跟软中断处理的cpu是同一个,这样就充分利用cpu的cache,这两个补丁往往都是一起设置,来达到最好的优化效果, 主要是针对单队列网卡多CPU环境。
图2:同时开启RPS/RFS后(来源网络)
rps_flow_cnt,rps_sock_flow_entries,参数的值会被进位到最近的2的幂次方值,对于单队列设备,单队列的rps_flow_cnt值被配置成与 rps_sock_flow_entries相同。
RFS依靠RPS的机制插入数据包到指定CPU的backlog队列,并唤醒那个CPU来执行
默认情况下,开启irqbalance是足够用的,但是对于一些对网络性能要求比较高的场景,手动绑定中断磨合是比较好的选择
开启irqbalance,会存在一些问题,比如:
a) 有时候计算出来的值不合理,导致CPU使用还是不均衡。
b) 在系统比较空闲IRQ处于 Power-save mode 时,irqbalance 会将中断集中分配给第一个 CPU,
以保证其它空闲 CPU 的睡眠时间,降低能耗。如果压力突然上升,可能会由于调整的滞后性带来性能问题。
c) 处理中断的CPU总是会变,导致了更多的context switch。
d)也存在一些情况,启动了irqbalance,但是并没有生效,没有真正去设置处理中断的cpu。
如何查看网卡的队列数
1,Combined代表队列个数,说明我的测试机有4个队列
# ethtool -l eth0
Channel parameters for eth0:
Pre-set maximums:
RX: 0
TX: 0
Other: 0
Combined: 4
Current hardware settings:
RX: 0
TX: 0
Other: 0
Combined: 4
2,以ecs centos7.6为例,系统处理中断的记录在/proc/interrupts文件里面,默认这个文件记录比较多,影响查看,同时如果cpu核心也非常多的话,对于阅读的影响非常大
# cat /proc/interrupts
CPU0 CPU1 CPU2 CPU3
0: 141 0 0 0 IO-APIC-edge timer
1: 10 0 0 0 IO-APIC-edge i8042
4: 807 0 0 0 IO-APIC-edge serial
6: 3 0 0 0 IO-APIC-edge floppy
8: 0 0 0 0 IO-APIC-edge rtc0
9: 0 0 0 0 IO-APIC-fasteoi acpi
10: 0 0 0 0 IO-APIC-fasteoi virtio3
11: 22 0 0 0 IO-APIC-fasteoi uhci_hcd:usb1
12: 15 0 0 0 IO-APIC-edge i8042
14: 0 0 0 0 IO-APIC-edge ata_piix
15: 0 0 0 0 IO-APIC-edge ata_piix
24: 0 0 0 0 PCI-MSI-edge virtio1-config
25: 4522 0 0 4911 PCI-MSI-edge virtio1-req.0
26: 0 0 0 0 PCI-MSI-edge virtio2-config
27: 1913 0 0 0 PCI-MSI-edge virtio2-input.0
28: 3 834 0 0 PCI-MSI-edge virtio2-output.0
29: 2 0 1557 0 PCI-MSI-edge virtio2-input.1
30: 2 0 0 187 PCI-MSI-edge virtio2-output.1
31: 0 0 0 0 PCI-MSI-edge virtio0-config
32: 1960 0 0 0 PCI-MSI-edge virtio2-input.2
33: 2 798 0 0 PCI-MSI-edge virtio2-output.2
34: 30 0 0 0 PCI-MSI-edge virtio0-virtqueues
35: 3 0 272 0 PCI-MSI-edge virtio2-input.3
36: 2 0 0 106 PCI-MSI-edge virtio2-output.3
input0说明是cpu1(0号CPU)处理的网络中断
阿里云ecs网络中断,如果是多个中断的话,还有input.1 input.2 input.3这种形式
......
PIW: 0 0 0 0 Posted-interrupt wakeup event
3,如果ecs的cpu核心非常多,那这个文件看起来就会比较费劲了,可使用下面的命令查看处理中断的核心
使用下面这个命令,即可将阿里云ecs处理中断的cpu找出来了(下面这个演示是8c 4个队列)
# for i in $(egrep "\-input." /proc/interrupts |awk -F ":" '{print $1}');do cat /proc/irq/$i/smp_affinity_list;done
5
7
1
3
处理一下sar拷贝用
# for i in $(egrep "\-input." /proc/interrupts |awk -F ":" '{print $1}');do cat /proc/irq/$i/smp_affinity_list;done |tr -s '\n' ','
5,7,1,3,
#sar -P 5,7,1,3 1 每秒刷新一次cpu 序号为5,7,1,3核心的cpu使用率
# sar -P ALL 1 每秒刷新所有核心,用于少量CPU核心的监控,这样我们就可以知道处理慢的原因是不是因为队列不够导致的了
Linux 3.10.0-957.5.1.el7.x86_64 (iZwz98aynkjcxvtra0f375Z) 05/26/2020 _x86_64_ (4 CPU)
05:10:06 PM CPU %user %nice %system %iowait %steal %idle
05:10:07 PM all 5.63 0.00 3.58 1.02 0.00 89.77
05:10:07 PM 0 6.12 0.00 3.06 1.02 0.00 89.80
05:10:07 PM 1 5.10 0.00 5.10 0.00 0.00 89.80
05:10:07 PM 2 5.10 0.00 3.06 2.04 0.00 89.80
05:10:07 PM 3 5.10 0.00 4.08 1.02 0.00 89.80
05:10:07 PM CPU %user %nice %system %iowait %steal %idle
05:10:08 PM all 8.78 0.00 15.01 0.69 0.00 75.52
05:10:08 PM 0 10.00 0.00 16.36 0.91 0.00 72.73
05:10:08 PM 1 4.81 0.00 13.46 1.92 0.00 79.81
05:10:08 PM 2 10.91 0.00 15.45 0.91 0.00 72.73
05:10:08 PM 3 9.09 0.00 14.55 0.00 0.00 76.36
sar 小技巧
打印idle小于10的核心
sar -P 1,3,5,7 1 |tail -n+3|awk '$NF<10 {print $0}'
看所有核心是否有单核打满的把1357换成ALL即可
sar -P ALL 1 |tail -n+3|awk '$NF<10 {print $0}'
再贴一个4c8g规格的配置(ecs.c6.xlarge ),
可以看到4c也给了四个队列,但是默认设置的是在cpu0 和 2上处理中断
# grep -i "input" /proc/interrupts
27: 1932 0 0 0 PCI-MSI-edge virtio2-input.0
29: 2 0 1627 0 PCI-MSI-edge virtio2-input.1
32: 1974 0 0 0 PCI-MSI-edge virtio2-input.2
35: 3 0 284 0 PCI-MSI-edge virtio2-input.3
# for i in $(egrep "\-input." /proc/interrupts |awk -F ":" '{print $1}');do cat /proc/irq/$i/smp_affinity_list;done
1
3
1
3
原因是cpu是超线程的,“每个vCPU绑定到一个物理CPU超线程”,所以即使是4个队列默认也在2个cpu核心上
# lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 4
On-line CPU(s) list: 0-3
Thread(s) per core: 2
Core(s) per socket: 2
Socket(s): 1
NUMA node(s): 1
4,关闭IRQbalance
# service irqbalance status
Redirecting to /bin/systemctl status irqbalance.service
● irqbalance.service - irqbalance daemon
Loaded: loaded (/usr/lib/systemd/system/irqbalance.service; enabled; vendor preset: enabled)
Active: inactive (dead) since Wed 2020-05-27 14:39:28 CST; 2s ago
Process: 1832 ExecStart=/usr/sbin/irqbalance --foreground $IRQBALANCE_ARGS (code=exited, status=0/SUCCESS)
Main PID: 1832 (code=exited, status=0/SUCCESS)
May 27 14:11:40 iZbp1ee4vpiy3w4b8y2m8qZ systemd[1]: Started irqbalance daemon.
May 27 14:39:28 iZbp1ee4vpiy3w4b8y2m8qZ systemd[1]: Stopping irqbalance daemon...
May 27 14:39:28 iZbp1ee4vpiy3w4b8y2m8qZ systemd[1]: Stopped irqbalance daemon.
5,手动设置RPS
5.1 手动设置之前我们需要先了解下面的文件(IRQ_number就是前面grep input拿到的序号)
进入/proc/irq/${IRQ_number}/,关注两个文件:smp_affinity和smp_affinity_list
smp_affinity是bitmask+16进制,
smp_affinity_list:这个文件更好理解,采用的是10进制,可读性高
改这两个任意一个文件,另一个文件会同步更改。
为了方便理解,咱们直接看十进制的文件smp_affinity_list即可
如果这一步没看明白,注意前面的 /proc/interrupts的输出
# for i in $(egrep "\-input." /proc/interrupts |awk -F ":" '{print $1}');do cat /proc/irq/$i/smp_affinity_list;done
1
3
1
3
手动设置处理中断的CPU号码可以直接echo修改,下面就是将序号27的中断放到cpu0上处理,一般建议可以把cpu0空出来
# echo 0 >> /proc/irq/27/smp_affinity_list
# cat /proc/irq/27/smp_affinity_list
0
关于bitmask
“f” 是十六进制的值对应的 二进制是”1111”(可以理解为4c的配置设置为f的话,所有的cpu参与处理中断)
二进制中的每个位代表了服务器上的每个CPU. 一个简单的demo
CPU序号 二进制 十六进制
CPU 0 0001 1
CPU 1 0010 2
CPU 2 0100 4
CPU 3 1000 8
5.2 需要对每块网卡每个队列分别进行设置。如对eth0的0号队列设置:
echo ff > /sys/class/net/eth0/queues/rx-0/rps_cpus
这里的设置方式和中断亲和力设置的方法是类似的。采用的是掩码的方式,但是这里通常要将所有的CPU设置进入,如:
4core,f
8core,ff
16core,ffff
32core,ffffffff
默认在0号cpu上
# cat /sys/class/net/eth0/queues/rx-0/rps_cpus
0
# echo f >>/sys/class/net/eth0/queues/rx-0/rps_cpus
# cat /sys/class/net/eth0/queues/rx-0/rps_cpus
f
6,设置RFS的方式
需要设置两个地方:
6.1, 全局表:rps_sock_flow_table的条目数量。通过一个内核参数控制:
# sysctl -a |grep net.core.rps_sock_flow_entries
net.core.rps_sock_flow_entries = 0
# sysctl -w net.core.rps_sock_flow_entries=1024
net.core.rps_sock_flow_entries = 1024
6.2,每个网卡队列hash表的条目数:
# cat /sys/class/net/eth0/queues/rx-0/rps_flow_cnt
0
# echo 256 >> /sys/class/net/eth0/queues/rx-0/rps_flow_cnt
# cat /sys/class/net/eth0/queues/rx-0/rps_flow_cnt
256
需要启动RFS,两者都需要设置。
建议机器上所有的网卡队列设置的rps_flow_cnt相加应该小于或者等于rps_sock_flow_entries。因为是4个队列,因此每个队列设置256,可以根据实际情况增大
某压测调整案例:
背景:iperf3 5个线程 1个1g打流量,抖动,如 4.6--5.2 波动较大,查看中断不均衡,做如下设置
1,设置RPS ,32c 16队列 设置ffffffff
2, RFS 65536 /16 = 4096
3, smp_affinity_list 1,3,5,7,9....31
依然不均衡,考虑到RFS 命中cache的问题,客户端增加到16线程,中断处理趋于稳定,但是流量依然有波动
4,关闭TSO后流量在7.9x--8.0x之间波动,趋于稳定