前不久生产碰到一个故障,一台宿主机上出现了大量的丢包,对业务造成了比较大的影响,遇到的问题还是蛮值得记录下来,所以简单的整理了下。

我们先了解几个概念:

什么是中断

CPU 工作的模式有两种,一种是中断,由各种设备发起;一种是轮询,由 CPU 主动发起。

我们主要看下中断。

中断又分为两种:一种硬中断;一种软中断。硬中断是由硬件产生的,比如,像磁盘,网卡,键盘;软中断是由当前正在运行的进程所产生的。

中断,是一种由硬件产生的电信号直接发送到中断控制器上,然后由中断控制器向 CPU 发送信号,CPU 检测到该信号后,会中断当前的工作转而去处理中断。然后,处理器会通知内核已经产生中断,这样内核就会对这个中断进行适当的处理。

举个例子:

当网卡收到数据包时会产生中断请求通知到 CPU,CPU 会中断当前正在运行的任务,然后通知内核有新数据包,内核调用中断处理程序进行响应,把数据包从网卡缓存及时拷贝到内存,否则会因为缓存溢出被丢弃。剩下的处理和操作数据包的工作就会交给软中断。

什么是多队列网卡

我们已经理解了中断,可是当网卡不断的接收数据包,就会产生很多中断,CPU 又如何能满足需求呢?

答案就是多队列网卡。

RSS(Receive Side Scaling)是网卡的硬件特性,实现了多队列。通过多队列网卡驱动加载,获取网卡型号,得到网卡的硬件 queue 的数量,并结合 CPU 核的数量,最终通过 Sum=Min(网卡 queue,CPU core)得出所要激活的网卡 queue 数量。

然后将各个 queue 中断分布到 CPU 多个核上,实现负载均衡,避免了单个核被占用到 100% 而其他核还处于空闲的情况。同一数据流会始终在同一 CPU 上,避免 TCP 的顺序性和 CPU 的并行性的冲突。基于流的负载均衡,解决了顺序协议和 CPU 并行的冲突以及 cache 热度问题。

多队列需要网卡硬件的支持。如果服务器的网卡支持 RSS,会在系统中看到网卡对应多个发送和接收队列:

root@SVR:~  # ls /sys/class/net/eth0/queues/
rx-0   rx-13  rx-18  rx-22  rx-27  rx-31  rx-36  rx-5  tx-0   tx-13  tx-18  tx-22  tx-27  tx-31  tx-36  tx-5
rx-1   rx-14  rx-19  rx-23  rx-28  rx-32  rx-37  rx-6  tx-1   tx-14  tx-19  tx-23  tx-28  tx-32  tx-37  tx-6
rx-10  rx-15  rx-2   rx-24  rx-29  rx-33  rx-38  rx-7  tx-10  tx-15  tx-2   tx-24  tx-29  tx-33  tx-38  tx-7
rx-11  rx-16  rx-20  rx-25  rx-3   rx-34  rx-39  rx-8  tx-11  tx-16  tx-20  tx-25  tx-3   tx-34  tx-39  tx-8
rx-12  rx-17  rx-21  rx-26  rx-30  rx-35  rx-4   rx-9  tx-12  tx-17  tx-21  tx-26  tx-30  tx-35  tx-4   tx-9

irq 亲缘绑定

/proc/interrupts 文件中可以看到各个 CPU 上的中断情况。

/proc/irq/[irq_num]/smp_affinity_list 可以查看指定中断当前绑定的 CPU。

我们主要关注网卡中断的 CPU 情况:

cat /proc/interrupts | grep mlx5_comp | cut -d: -f1 | while read i; do echo -ne irq":$i\t bind_cpu: "; cat /proc/irq/$i/smp_affinity_list; done | sort -n -t' ' -k3

输出是这样的:

irq:100	 bind_cpu: 10-19,30-39
irq:101	 bind_cpu: 10-19,30-39
irq:102	 bind_cpu: 10-19,30-39
irq:103	 bind_cpu: 10-19,30-39
irq:112	 bind_cpu: 10-19,30-39
irq:113	 bind_cpu: 10-19,30-39
irq:114	 bind_cpu: 10-19,30-39
irq:115	 bind_cpu: 10-19,30-39
irq:116	 bind_cpu: 10-19,30-39
irq:117	 bind_cpu: 10-19,30-39
irq:118	 bind_cpu: 10-19,30-39
irq:119	 bind_cpu: 10-19,30-39
irq:120	 bind_cpu: 10-19,30-39
irq:121	 bind_cpu: 10-19,30-39
irq:122	 bind_cpu: 10-19,30-39
irq:123	 bind_cpu: 10-19,30-39
......

可以看到绑定的是一组 CPU。

我们再查看下具体的中断情况:

# cat /proc/interrupts | grep mlx5_comp | tr -s ' ' '\t'|cut -f 1-15
	88:	0	0	0	0	0	0	0	0	0	0	1577579474	0	0
	89:	0	0	0	0	0	0	0	0	0	0	3677007833	1	0
	90:	0	0	0	0	0	0	0	0	0	0	1084594339	0	1
	91:	0	0	0	0	0	0	0	0	0	0	2274784560	0	0
	92:	0	0	0	0	0	0	0	0	0	0	1141602063	0	0
	93:	0	0	0	0	0	0	0	0	0	0	2788053592	0	0
	94:	0	0	0	0	0	0	0	0	0	0	2929294228	0	0
	95:	0	0	0	0	0	0	0	0	0	0	3437304297	0	0
	96:	0	0	0	0	0	0	0	0	0	0	4209729499	0	0
	97:	0	0	0	0	0	0	0	0	0	0	296747534	0	0
	98:	0	0	0	0	0	0	0	0	0	0	2753415640	0	0
	99:	0	0	0	0	0	0	0	0	0	0	2542583067	0	0
	100:	0	0	0	0	0	0	0	0	0	0	3961165575	0	0
	101:	0	0	0	0	0	0	0	0	0	0	1548441749	0	0
	102:	0	0	0	0	0	0	0	0	0	0	2205752712	0	0
	103:	0	0	0	0	0	0	0	0	0	0	3285051802	0	0
	112:	0	0	0	0	0	0	0	0	0	0	2039544026	0	0
	113:	0	0	0	0	0	0	0	0	0	0	994776868	0	0
	114:	0	0	0	0	0	0	0	0	0	0	3212436506	0	0
	115:	0	0	0	0	0	0	0	0	0	0	615608559	0	0
	116:	2	0	0	0	0	0	0	0	0	0	1387799451	0	0
	117:	0	1	0	0	0	0	0	0	0	0	1704941483	0	0
	118:	0	0	1	0	0	0	0	0	0	0	1605432559	0	0
	119:	0	0	0	1	0	0	0	0	0	0	3932507321	0	0
	120:	0	0	0	0	1	0	0	0	0	0	1610073075	0	0
	121:	0	0	0	0	0	1	0	0	0	0	1114634191	0	0
	122:	0	0	0	0	0	0	1	0	0	0	3338484401	0	0
	123:	0	0	0	0	0	0	0	1	0	0	1982853886	0	0

可以看到中断都是被 CPU10 处理的。查看 mellanox 的资料 what-is-irq-affinity-x

In case the IRQ affinity is not tuned, it mapped differently. For example, each interrupt to all CPUs you will get something like “FF” while running the command “show_irq_affinity”. This is less recommended, as in most cases, only the lower CPU cores (e.g. CPU0, CPU1) will be used and congested due to high volume of interrupts while the higher CPUs will not get to answer interrupts.

也就是说这里配置了 10-19,30-39,但是只有 CPU10 在处理中断。

丢包的原因

上面已经分析了中断都是被 CPU10 处理了,我们再查看下 softnet_stat 的情况:

# cat /proc/net/softnet_stat
dcc6cc07 00006e12 000001ef 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
199c3900 00000000 0000001b 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
e576cfcb 00000000 00000016 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
c3c17454 00000000 00000013 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
abb700af 00000000 00000010 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
98ceb017 00000000 0000000c 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
89c0460b 00000000 0000000c 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
7d3d0e6a 00000000 0000000e 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
724680ee 00000000 00000008 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
68f63700 00000000 00000013 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
b4f004b0 30b8190e 00180514 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
bdfe8473 00037a32 0000022a 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
8b1c5f73 0001a1ee 000001b5 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
f107894f 0000e7f9 00000181 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
7663ef78 0000a29f 00000120 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
76275161 00008367 000000e2 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
......

其中: 每一行代表每个 CPU 核的状态统计,从 CPU0 依次往下; 每一列代表一个 CPU 核的各项统计:第一列代表中断处理程序收到的包总数;第二列即代表由于 netdev_max_backlog 队列溢出而被丢弃的包总数。

可以看到 CPU10 有大量的丢包现象(第二列数据)。

对 softnet_stat 数据我们做了一些监控,可以看到的定期会有丢包的现象。

关于丢包,扩展阅读可以看我之前一篇博客 linux 丢包那些事

如何处理

netdev_max_backlog 调优

netdev_max_backlog 是内核从 NIC 收到包后,交由协议栈(如 IP、TCP )处理之前的缓冲队列。

从这次现象看,因为超越了设定的 netdev_max_backlog 值,导致数据包被丢弃。netdev_max_backlog 的默认值是 1000,我们可以修改内核参数来调优:

sysctl -w net.core.netdev_max_backlog=2000

网卡中断均衡

调优 netdev_max_backlog 只是一个 workaround 方案,本质还是因为网卡中断分配不均导致,所以我们需要讲中断均衡,方案如下:

网卡绑定

网卡绑定就是将网卡中断手动绑定到不同 CPU,前面简单介绍了下 smp_affinity_list,我们再来看下。

/proc/irq/[irq_num]/smp_affinity

该文件存放的是 CPU 位掩码(十六进制)。修改该文件中的值可以改变 CPU 和某中断的亲和性。

/proc/irq/[irq_num]/smp_affinity_list

该文件存放的是 CPU 列表(十进制)。注意,CPU 核心个数用表示编号从 0 开始,如 CPU0, CPU1 等。

所以我们手动的对网卡的各个队列进行 CPU 绑定,如下:

echo 0 > /proc/irq/101/smp_affinity_list
echo 0 > /proc/irq/102/smp_affinity_list
echo 1 > /proc/irq/103/smp_affinity_list
echo 1 > /proc/irq/104/smp_affinity_list
echo 2 > /proc/irq/105/smp_affinity_list
echo 2 > /proc/irq/106/smp_affinity_list
......

中断绑定后, 我们查看 /proc/interrupts 就可以看到中断会分布在 CPU 多个核上。

35:	492825	0	0	0	0	0	0	0	0	0	0	0	0
36:	0	421113	0	0	0	0	0	0	0	0	0	0	0
37:	0	0	384418	0	0	0	0	0	0	0	0	0	0
38:	0	0	0	371091	0	0	0	0	0	0	0	0	0
39:	0	0	0	0	362977	0	0	0	0	0	0	0	0
40:	0	0	0	0	0	352131	0	0	0	0	0	0	0
41:	0	0	0	0	0	0	326605	0	0	0	0	0	0
42:	0	0	0	0	0	0	0	366171	0	0	0	0	0
43:	0	0	0	0	0	0	0	0	353674	0	0	0	0
44:	0	0	0	0	0	0	0	0	0	321370	0	0	0
45:	0	0	0	0	0	0	0	0	0	0	342732	0	0
46:	0	0	0	0	0	0	0	0	0	0	0	321248	0
47:	0	0	0	0	0	0	0	0	0	0	0	0	324321

irqbalance 和 minx_tune

这两块我想作为扩展阅读,后面提供一些资料,对于 irqbalance 补充一点,irqbalance 是根据系统中断负载的情况,自动迁移中断保持中断的平衡,同时会考虑到省电因素等等。但是在实时系统中会导致中断自动漂移,对性能造成不稳定因素,在高性能的场合建议关闭。

irqbalance- Linux man page
霸爷的文章:深度剖析告诉你irqbalance有用吗?
How to Tune Your Linux Server for Best Performance Using the mlnx tune Tool

总结

关于 irq 其实是比较常见的问题,在很多优化的场景都需要将 irq 和处理进程绑定到不同的 CPU 上以提供更好的实时响应。博客也介绍了需要关注的几个指标和解决方案,希望记录下来对大家有所帮助。