关于Linux cpu中断问题及案例

我们经常碰到整体cpu不高,但是性能不佳的案例,这种案例往往跟CPU 处理中断的核心跑满有关系,话不多说,我们来看看中断相关的设置与案例。


什么是中断?
当一个硬件(如磁盘控制器或者以太网卡), 需要打断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直接绑定硬中断。
关于Linux cpu中断问题及案例

图1 只有RPS的情况下(来源网络)

由于RPS只是单纯把数据包均衡到不同的cpu,这个时候如果应用程序所在的cpu和软中断处理的cpu不是同一个,此时对于cpu cache的影响会很大,那么RFS(Receive flow steering)确保应用程序处理的cpu跟软中断处理的cpu是同一个,这样就充分利用cpu的cache,这两个补丁往往都是一起设置,来达到最好的优化效果, 主要是针对单队列网卡多CPU环境。
关于Linux 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之间波动,趋于稳定
通过上面的设置,我们可以在实际的压测中做一些微调测试观察业务,也可以看看生产环境中在用的实例cpu中断是否均衡呢?

某客户cpu中断不工作的案例:

背景:客户反馈自建了几台代理服务器,反向代理后端的k8s集群,目前有2台实例偶发的超时,同时后端应用没有日志记录
1,客户提供了代理服务器的抓包文件,通过抓包看到syn重传对端没回包,判断问题可能在对端
关于Linux cpu中断问题及案例

2,server端抓包发现重传报文未到server端,视线回到源实例上
3,物理机抓包排查,发现重传报文未到物理机上,说明是系统内部出现问题
4,登陆系统内部检查中断的设置,这个实例是8c,8个队列,默认设置了0246核心处理这8个中断
关于Linux cpu中断问题及案例

5,查看各个核心上处理中断的数量,发现偏差较大,同时发现input6 6号中断有问题,监控良久未变化
关于Linux cpu中断问题及案例

6,检查系统日志,看看input6是否有异常日志,果不其然有个异常的告警,疑似qemu-kvm的bug
关于Linux cpu中断问题及案例

7,重启接恢复,同时参考上面的设置,把中断绑定到不同的核心上试试

上一篇:Linux误操作777之后的恢复方法


下一篇:k8s网络诊断之我的流量去哪了