高并发编程-2 软中断
2022-09-07 00:00:00

说明

这篇文章前面相关内容都是小林coding文章中的, 我太菜了而且对这些了解也不深,借用大佬的描述来了解软中断。

软中断也是导致CPU利用率过高的一种原因,在高并发情况下,比如lvs和haproxy,这种代理的性能非常高,转发网络包的数据很快,但是有时候CPU没满,性能却上不去,这个就有可能是软中断导致的,因为有的机器软中断只会在其中几个核处理,当跑满这个CPU核时,系统已经没办法接收更多数据了,吞吐量也就上不去。

中断

在计算机中,中断是系统用来响应硬件设备请求的一种机制,操作系统收到硬件的中断请求,会打断正在执行的进程,然后调用内核中的中断处理程序来响应请求。

  • 中断是一种异步的事件处理机制,可以提高系统的并发处理能力

由于中断处理程序会打断其他进程的运行,为了减少对正常进程运行调度的影响,中断处理程序就需要尽可能快地运行
而且,中断处理程序在响应中断时,可能还会「临时关闭中断」,这意味着,如果当前中断处理程序没有执行完之前,系统中其他的中断请求都无法被响应,也就说中断有可能会丢失,所以中断处理程序要短且快

软中断

Linux 系统为了解决中断处理程序执行过长和中断丢失的问题,将中断过程分成了两个阶段,分别是「上半部和下半部分」。

  • 上半部用来快速处理中断,一般会暂时关闭中断请求,主要负责处理跟硬件紧密相关或者时间敏感的事情。
  • 下半部用来延迟处理上半部未完成的工作,一般以「内核线程」的方式运行。

网卡接收数据包

网卡收到网络包后,会通过硬件中断通知内核有新的数据到了,于是内核就会调用对应的中断处理程序来响应该事件,这个事件的处理也是会分成上半部和下半部。
上部分要做到快速处理,所以只要把网卡的数据读到内存中,然后更新一下硬件寄存器的状态,比如把状态更新为表示数据已经读到内存中的状态值。

接着,内核会触发一个软中断,把一些处理比较耗时且复杂的事情,交给「软中断处理程序」去做,也就是中断的下半部,其主要是需要从内存中找到网络数据,再按照网络协议栈,对网络数据进行逐层解析和处理,最后把数据送给应用程序。

所以,中断处理程序的上部分和下半部可以理解为:

  • 上半部直接处理硬件请求,也就是硬中断,主要是负责耗时短的工作,特点是快速执行;
  • 下半部是由内核触发,也就说软中断,主要是负责上半部未完成的工作,通常都是耗时比较长的事情,特点是延迟执行;

还有一个区别,硬中断(上半部)是会打断 CPU 正在执行的任务,然后立即执行中断处理程序,而软中断(下半部)是以内核线程的方式执行,并且每一个 CPU 都对应一个软中断内核线程,名字通常为「ksoftirqd/CPU 编号」,比如 0 号 CPU 对应的软中断内核线程的名字是 ksoftirqd/0

不过,软中断不只是包括硬件设备中断处理程序的下半部,一些内核自定义事件也属于软中断,比如内核调度等、RCU 锁(内核里常用的一种锁)等。

排查软中断导致的性能问题

在Centos中可以使用cat /proc/softirqs这个命令查看软中断的运行情况

1
2
3
4
5
6
7
8
9
10
11
12
[root@localhost ~]# cat /proc/softirqs
CPU0 CPU1 CPU2 CPU3 CPU4 CPU5 CPU6 CPU7
HI: 4 0 0 0 0 0 0 0
TIMER: 532198655 459491084 471888996 564053345 463175289 446754977 2240971851 764917002
NET_TX: 1762126 38718 35142 43037 15838 18829 236006 2104955878
NET_RX: 15538808 16475626 18623151 17157923 11507031 16678401 2123413007 20772910
BLOCK: 1493868 26037 116512 23542 10995001 3787614 992 3937814
BLOCK_IOPOLL: 0 0 0 0 0 0 0 0
TASKLET: 69 4 1 3 16 3 1 187326156
SCHED: 281338291 229366217 229384601 299741463 159036030 154905926 840990582 259952209
HRTIMER: 0 0 0 0 0 0 0 0
RCU: 375348322 332454839 344330542 398191959 318119015 311244918 1114199771 507090060
  • 每一个 CPU 都有自己对应的不同类型软中断的累计运行次数
  • 第一列的内容,它是代表着软中断的类型,其中NET_RX 表示网络接收中断NET_TX 表示网络发送中断
  • 从我这台服务器上可以看出CPU6的NET_RX次数很大,CPU7的NET_TX次数很大,这是因为我进行过多次压测,而这2个核就是在处理对应网卡的软中断。

使用命令cat /proc/interrupts 可以查看硬中断的运行情况。

还有最常用的命令 top 可以查看CPU的使用情况,运行top后按1就可以查看每一个cpu核的使用情况, 这是一个正在接收压力的服务器情况,可以看到8个核的利用率

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
top - 18:27:09 up 100 days,  9:15,  1 user,  load average: 1.11, 0.54, 0.47
Tasks: 196 total, 4 running, 191 sleeping, 0 stopped, 1 zombie
%Cpu0 : 10.8 us, 11.4 sy, 0.0 ni, 77.8 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu1 : 10.1 us, 12.5 sy, 0.0 ni, 77.4 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu2 : 12.8 us, 14.1 sy, 0.0 ni, 73.2 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu3 : 9.5 us, 11.6 sy, 0.0 ni, 78.9 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu4 : 9.4 us, 4.7 sy, 0.0 ni, 86.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu5 : 16.5 us, 8.4 sy, 0.0 ni, 75.1 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu6 : 2.0 us, 1.7 sy, 0.0 ni, 45.1 id, 0.0 wa, 0.0 hi, 51.2 si, 0.0 st
%Cpu7 : 9.2 us, 5.8 sy, 0.0 ni, 78.6 id, 0.0 wa, 0.0 hi, 6.4 si, 0.0 st
KiB Mem : 15960012 total, 160152 free, 5327740 used, 10472120 buff/cache
KiB Swap: 16777212 total, 16629500 free, 147712 used. 10256480 avail Mem

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
6464 root 20 0 1499768 40612 1668 R 37.2 0.3 198:03.65 nginx
6465 root 20 0 1503432 41740 1884 S 31.9 0.3 219:19.11 nginx
6462 root 20 0 1494748 40528 1668 R 28.2 0.3 157:15.04 nginx
6460 root 20 0 1492084 39168 1700 R 19.6 0.2 107:35.52 nginx
6463 root 20 0 1489640 38520 1916 S 11.3 0.2 61:29.39 nginx

其中

  • us:用户空间占用CPU百分比(Host.cpu.user)
  • sy:内核空间占用CPU百分比(Host.cpu.system)
  • ni:用户进程空间内改变过优先级的进程占用CPU百分比
  • id:空闲CPU百分比(Host.cpu.idle)
  • wa:等待输入输出的CPU时间百分比
  • hi:硬件中断
  • si:软件中断
  • st:实时

可以看到us sy每个核都有使用,但是使用率不高,而CPU6的si(软中断)很高,有50%多,这是因为发压的数据请求字节数很大,响应字节数很小,而上面我提过这台机器CPU6处理网络接收中断的,所以进入的数据流会很大,也就是会更占CPU。

从这个情况就可以扩展想一下,整体的CPU我们可以看到利用率不高,因为每个核的空闲百分比都很大(70%多),但是软中断的CPU6则利用率有50%多,所以这个软中断就很有可能成为性能瓶颈,也就是当请求量增加到一定程度,CPU6会直接被打满,而其他CPU核往往还有大量空闲。

这是部署Nginx的一个情况,HaProxy和LVS等等都一样,都会受到这个限制,所以当发现吞吐量上不去,而CPU利用率也比较低的时候就应该看看是不是软中断导致的

解决方法

如果排查到软中断导致性能问题,解决方法可以从以下几方面考虑

  1. 硬件升级,CPU越好,那么处理速度越快,就能缓解这个问题,但是得加钱。
  2. 网卡多队列、RSS,可以查看是否支持多队列,比如通过命令cat /proc/interrupts,如果是以下这种情况,可以看到有多个核都能处理网卡中断,那么这种性能会高很多。有的博客提到过可以修改参数配置,让网卡中断绑定到不同的核上,但是我试过不行,可能是操作不对或者系统不支持,后面也没深入研究,如果有这种需求可以再找一找相关资料。
    1
    2
    3
    4
    5
    6
    7
    8
    9
           CPU0       CPU1       CPU2       CPU3       CPU4       CPU5       CPU6       CPU7
    35: 1157411688 0 0 0 0 0 0 0 IR-PCI-MSI-edge eth0-TxRx-0
    36: 2199548159 0 0 0 0 0 0 0 IR-PCI-MSI-edge eth0-TxRx-1
    37: 2210448541 0 0 0 0 0 0 0 IR-PCI-MSI-edge eth0-TxRx-2
    38: 954381730 0 0 0 0 0 0 0 IR-PCI-MSI-edge eth0-TxRx-3
    39: 3991435919 0 0 0 0 0 0 0 IR-PCI-MSI-edge eth0-TxRx-4
    40: 2197207910 0 0 0 0 0 0 0 IR-PCI-MSI-edge eth0-TxRx-5
    41: 2685654183 0 0 0 0 0 0 0 IR-PCI-MSI-edge eth0-TxRx-6
    42: 2153260851 0 0 0 0 0 0 0 IR-PCI-MSI-edge eth0-TxRx-7
  3. DPDK ,DPDK 抛弃了传统的内核中断,采用轮询模式驱动( poll mode driver,PMD) 的方式直接操作网卡的接收和发送队列,将报文直接拷贝到用户空间,不再经过内核协议栈。这样就不会受到大量中断影响了,性能也会更高,比如可以看美团的MGV,也可以搜一下其他相关的技术。
  4. 使用不同的负载均衡方式,DNS、LVS、硬件负载等等