解决网卡丢包问题,以及丢包问题解决后系统网络还是慢的问题

背景

线上的K8S集群有一台节点,装了Istio的遥测组件,这个组件,之前在哪个节点哪个节点就出问题。出现的问题主要体现在几个点:

①:服务器间歇性 NodeNotReady
②:服务器丢包极为严重
③:服务器网络非常慢,以至于在服务器上执行 curl youku.com 都要响应很久。

Istio的遥测组件,是一个服务,由Istio的Ingress以及SideCar发送数据给遥测服务,而且发送的是UDP的包,量非常非常大,能达到 500Mbps。UDP包量大应该是导致了网卡丢包以及网络缓慢的主要问题,所以,要解决这个问题,就需要坐下Linux网卡收包层面的优化。开始优化前,需要先明确一下步骤:

  • 几个基本概念
  • 网卡相关的基础知识
  • 网卡收包问题排查

几个基本概念

NIC:NIC 是 Network Interface Card,即网络接口卡,也就是网卡。

IRQ:传统上,NIC会生成中断请求(IRQ),指示数据已到达。IRQ也是有多种的,有三种常见类型的IRQ:MSI-X,MSI和传统IRQ。

网卡相关的基础知识

网卡的队列数量

查看和设置网卡使用的队列数量(注意,是队列的数量,不是队列本身的大小,仅针对收包)

1
2
// 小写 l 查看
ethtool -l em2
1
2
3
4
5
6
7
8
9
10
11
Channel parameters for em2:
Pre-set maximums:
RX: 4
TX: 4
Other: 0
Combined: 0
Current hardware settings:
RX: 4
TX: 1
Other: 0
Combined: 0

从上面看,队列有几种:RX、TX、Combined 等。有些网卡,只支持 Combined 队列(发送和接收公用,这种叫做组合队列),有些可以单独设置 RX 和 TX 队列(RX 就是接收队列,TX 就是发送队列)。

设置网卡队列的大小

1
2
// 大写 L 为设置
ethtool -L eth0 rx 8

注意:

①:并非所有网卡都支持这个操作命令,这得看网卡驱动是否支持 ethtool get_channels 操作,一般支持这个操作的是物理网卡,虚拟网卡、虚拟网桥一般不支持。
②:设置队列操作,会导致网卡关闭后再次启动,所以和这个网卡相关的链接就会关闭。

网卡的队列大小

查看和设置网卡的队列大小(注意是大小,不是数量)

查看网卡队列大小

1
2
// 小写 g 为查看
ethtool -g em2
1
2
3
4
5
6
7
8
9
10
11
Ring parameters for em2:
Pre-set maximums:
RX: 2047
RX Mini: 0
RX Jumbo: 0
TX: 511
Current hardware settings:
RX: 200
RX Mini: 0
RX Jumbo: 0
TX: 511

这个设置说明,网卡本身的队列大小,支持最高 2047,目前的设置是 200。

设置网卡队列大小

1
2
// 大写 G 为设置
ethtool -G eth0 rx 4096

注意:

①:并不是所有网卡都支持通过 ethtool 查看和修改网卡队列大小。
②:这个操作,同样会关闭和启动网卡,所以,和这个网卡相关的连接也会中断。

网卡的队列权重

调整网卡队列的处理权重(仅针对收包)

查看网卡队列的权重

1
2
// 小写 x 为查看
ethtool -x em2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
RX flow hash indirection table for em2 with 4 RX ring(s):
0: 0 1 2 3 0 1 2 3
8: 0 1 2 3 0 1 2 3
16: 0 1 2 3 0 1 2 3
24: 0 1 2 3 0 1 2 3
32: 0 1 2 3 0 1 2 3
40: 0 1 2 3 0 1 2 3
48: 0 1 2 3 0 1 2 3
56: 0 1 2 3 0 1 2 3
64: 0 1 2 3 0 1 2 3
72: 0 1 2 3 0 1 2 3
80: 0 1 2 3 0 1 2 3
88: 0 1 2 3 0 1 2 3
96: 0 1 2 3 0 1 2 3
104: 0 1 2 3 0 1 2 3
112: 0 1 2 3 0 1 2 3
120: 0 1 2 3 0 1 2 3

说明:如上,有4个队列,队列编号是:0、1、2、3。散列值为2的数据,要发往2号队列,散列值为3的,要发往3号队列,散列值为8的,要发往0号队列。

设置网卡队列权重

1
2
3
4
5
6
7
8
// 大写 X 为设置

// 设置权重均分,equal
ethtool -X eth0 equal 2

// 单独设置权重
// 下面这个操作,是为网卡的0号队列设置权重为6,网卡的1号队列设置权重为2。这样一来,队列0会处理更多的数据
ethtool -X eth0 weight 6 2

针对网卡队列的网卡收包的哈希策略

调整网卡上收包(RX)过程用于哈希的字段(仅针对收包)

首先,对数据包哈希的意义,是用于放入不同的队列。哈希可以选择数据包中的不同字段来哈希。

查看用于哈希的字段:

1
2
// 小写 n 查看
ethtool -n eth0 rx-flow-hash udp4
1
2
3
UDP over IPV4 flows use these fields for computing Hash flow key:
IP SA
IP DA

上面的输出说明,计算接受UDP包的哈希字段,是 IP地址的源地址和目的地址。

配置用于哈希的字段:

1
2
// 大写的 N 为设置
ethtool -N eth0 rx-flow-hash udp4 sdfn

其中 sdfn 是一个字符串,可以通过 man ethtool 来查看。

网卡 RX的 ntuple 过滤功能(仅针对收包)

有一些网卡,是支持“ntuple过滤”的功能。这个功能用于过滤硬件中的传入网络数据并将其排入特定的RX队列。这个可以做到很多过滤特性,比如:

①:用户可以指定发往特定端口的TCP数据包应该发送到RX队列1。换句话说,让发往80端口的是数据包,指定到某个队列。
②:让发往某个端口的数据,由某个CPU处理。

这个功能,有不同的叫法,比如说,Intel 的网卡管这个叫做 Internet Ethernet Flow Director。

举一个此功能的使用场景:提供Web服务的响应效率,步骤如下:

①:让80上运行的Web服务器固定在CPU 2上运行(也就是对进程的CPU绑定)。
②:RX队列(收包队列)的IRQ(请求中断)被分配为由CPU 2处理。也就是,让某个收包队列,由某个CPU处理。
③:发往端口80的TCP流量使用ntuple“过滤”到CPU 2。

正题,查看网卡是否支持 ntuple 过滤:

1
2
// 小写 k 查看
ethtool -k eth1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Features for eth1:
rx-checksumming: off [fixed]
tx-checksumming: on
tx-checksum-ipv4: off [fixed]
tx-checksum-ip-generic: on
tx-checksum-ipv6: off [fixed]
tx-checksum-fcoe-crc: off [fixed]
tx-checksum-sctp: off [fixed]
scatter-gather: on
tx-scatter-gather: on
tx-scatter-gather-fraglist: off [fixed]
tcp-segmentation-offload: on
tx-tcp-segmentation: on
tx-tcp-ecn-segmentation: off [fixed]
tx-tcp6-segmentation: on
tx-tcp-mangleid-segmentation: off
....
ntuple-filters: off [fixed]
....
hw-tc-offload: off [fixed]

如上,nutple-filters 为 off ,表示 ntuple 过滤功能没有开启。

开启 nutple 过滤:

1
2
// 大写 K 设置
ethtool -K eth0 ntuple on

查看 ntuple 过滤规则:

1
2
// 小写 u 查看规则
ethtool -u eth0
1
2
40 RX rings available
Total 0 rules

如上,Total 0 rules 表示此设备没有 ntuple 过滤规则。

添加 ntuple 过滤规则:

1
2
3
// 大写 U 设置规则
// 添加 ntuple 过滤规则,让目标端口80的TCP流发送到RX队列2
ethtool -U eth0 flow-type tcp4 dst-port 80 action 2

注意:可以使用ntuple过滤在硬件级别丢弃特定流的数据包。比如,丢弃特定IP地址的传入流量。

软中断(softirq)

中断简介:

中断:外部设备,需要通过中断机制(中断信号),来通知CPU处理响应的任务。这样CPU就响应中断信号对应的处理动作即可。避免CPU盲目等待某一个任务而无法处理其他任务的情况。中断是由外部设备引起的,计算机能够接受的外部信号,非常的有限,因为不可能为每一个外部的设备都定义好信号的格式,所以,计算机给外部信号,只约定了一种信号格式,这种信号就是中断信号,这套信号的接受和处理共同组成中断机制。

计算机的中断,可以看做一个过程,它在看电视,然后水烧开了(中断信号),基于计算机去处理烧水的事情。但是计算机不具备连续性,处理完了烧水的事儿,就不知道怎么回到原来的过程再去看电视。计算机要完成上面的看似具备逻辑性的事情,就需要硬件+软件协同,实现处理中断的全部过程。

中断需要尽可能的快,软中断(softirq)的意义就是,处理中断没有处理完的事情。

举例来说:比如在网卡的驱动程序里,在中断环境里,只是把包放到一个队列里,然后由软中断来把包传递给进程,或者转发包等。

Linux 里边的软中断不可重入,在一个CPU上,一次只能有一个软中断在执行。多个CPU的话,可以有多个软中断。

查看当前机器的系统中断

1
2
3
4
5
// 第一步
top
// 第二步
1
// si 列,就是系统中断,si 就是 softirqs

查看软中断统计信息:

1
cat /proc/softirqs
1
2
3
4
5
6
7
8
9
10
11
                    CPU0       CPU1       CPU2       CPU3
HI: 0 0 0 0
TIMER: 2831512516 1337085411 1103326083 1423923272
NET_TX: 15774435 779806 733217 749512
NET_RX: 1671622615 1257853535 2088429526 2674732223
BLOCK: 1800253852 1466177 1791366 634534
BLOCK_IOPOLL: 0 0 0 0
TASKLET: 25 0 0 0
SCHED: 2642378225 1711756029 629040543 682215771
HRTIMER: 2547911 2046898 1558136 1521176
RCU: 2056528783 4231862865 3545088730 844379888

可以看到,不同的 CPU 处理关于网卡收包的软中断次数不一样(速度不一样),并不均匀,这说明有其他的因素,影响了这个速度。

IRQ coalescing(中断合并)

这个东西,允许中断前缓存数据包,如果简单来说,一个包就会触发中断,那么它可以做到10个包触发一次中断,有效降低中断次数,提高系统响应效率。

启用自适应RX / TX IRQ合并的结果是,当数据包速率较低时,将调整中断传送以改善延迟,并在数据包速率较高时提高吞吐量。

查看网卡的中断合并是否已开启

1
ethtool -c eth0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Coalesce parameters for eth0:
Adaptive RX: off TX: off
stats-block-usecs: 0
sample-interval: 0
pkt-rate-low: 0
pkt-rate-high: 0

rx-usecs: 20
rx-frames: 5
rx-usecs-irq: 0
rx-frames-irq: 5

tx-usecs: 72
tx-frames: 53
tx-usecs-irq: 0
tx-frames-irq: 5

rx-usecs-low: 0
rx-frame-low: 0
tx-usecs-low: 0
tx-frame-low: 0

rx-usecs-high: 0
rx-frame-high: 0
tx-usecs-high: 0
tx-frame-high: 0

其中,Adaptive 显示了当前 RX 和 TX 是否开启了中断合并。

设置中断合并

1
ethtool -C eth0 adaptive-rx on

IRQ(中断)的CPU亲和性

对于支持多队列的网卡来说,可以尝试让特定的CPU来处理NIC生成的中断。通过设置特定的CPU,可以分割将用于处理哪些IRQ的CPU。

不过,如果要调整IRQ亲和力,首先应该检查是否运行 irqbalance 守护程序。 这个守护进程尝试自动平衡IRQ到CPU,所以,在开启了 irqbalance 的情况下,它可能会覆盖你原有的设置。 如果正在运行 irqbalance,则应该禁用 irqbalance 或将 –banirq 与 IRQBALANCE_BANNED_CPUS 结合使用,或者,让 irqbalance 知道它不应该触及你想要自己分配的一组 IRQ 和 CPU 。

查看是否开启了 irqbalance 服务

1
systemctl status irqbalance

如果没有开启,可以将其开启,开启后,网卡硬中断所绑定的CPU,将由 irqbalance 来自动调配。

netdev_max_backlog

inux 内核从网卡驱动中读取报文后可以缓存的报文数量,默认是 1000。积压在 backlog 的数据包,就是由软中断进行处理的。

调整方式:

1
sysctl -w net.core.netdev_max_backlog=2000

注意:检查你网卡的驱动程序,如果它调用 netif_receive_skb 并且你没有使用 RPS,那么增加 netdev_max_backlog 将不会产生任何性能提升,因为没有数据会进入 input_pkt_queue。

套接字接收队列内存

可以通过下面的方式,调整 socket 接收队列的缓冲区大小

1
2
// 8388608 就是 8M 左右, 26214400 就是25M 左右
sysctl -w net.core.rmem_max=8388608

sk_rcvbuf 从 net.core.rmem_default 值开始,所以,也可以通过设置sysctl来调整 net.core.rmem_default

1
sysctl -w net.core.rmem_default=8388608

我们的应用程序,也可以调用 setsockopt 的时候,配置 SO_RCVBUF 来决定套接字接收队列的内存大小,不过,你最大能设置的值,无法超出 rmem_max (注意:这个说法不完全准确,因为可以通过调用 setsockopt 并传递 SO_RCVBUFFORCE 来覆盖net.core.rmem_max限制,但是,需要你具备 CAP_NET_ADMIN 的能力)

网卡收包排查

先解决丢包问题

要排查问题,说明已经存在问题,存在的问题比如,服务失败率变高(HTTP服务接口失败率高)->进而发现网卡流量太大->进而发现网卡丢包。

先从丢包问题入手,因为有些场景下,解决丢包问题即是第一步,可能这个问题解决了,服务响应问题也就解决了。

查看丢包问题

1
ifconfig eth0

查看其中的 dropped 是否为0,以及是否递增。

通过下面几种操作,往往可以解决丢包问题:

1
2
3
4
5
6
// 增大socket缓冲区上限
sysctl -w net.core.rmem_max=8388608
// 增大socket缓冲区默认大小
sysctl -w net.core.rmem_default=8388608
// 调整 Linux 系统的网卡缓冲区大小
sysctl -w net.core.netdev_max_backlog=2000

可以通过监控系统进行观测,或者ifconfig进行查看。

虽然网卡不丢包了,系统还是慢,甚至一个 curl youku.com 都能明显感觉卡顿,这种情况,就需要进一步探究。

排查网卡软中断过高的问题

首先,网卡相关的事件,是会发生硬中断和软中断。

①:硬中断,网卡收包就会触发。
②:软中断,网卡收包后,系统将其后续处理,比如发往进程,或者进行包转发等。

软中断,一般是一个或者多个CPU来处理,现在很多网卡都是支持多队列网卡,这种情况,每一个队列都可能绑定一个CPU做中断处理。所以,我们先要确认一下,我们在使用的网卡,有几个队列,每个队列由哪个CPU在处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 查看网卡 RX 的队列数量
ethtool -l em1

// 下面是输出,说明收包丢列(RX)有4个。
Channel parameters for em1:
Pre-set maximums:
RX: 4
TX: 4
Other: 0
Combined: 0
Current hardware settings:
RX: 4
TX: 1
Other: 0
Combined: 0
1
2
// 查看系统中断统计
cat /proc/interrupts

找到对应的网卡,比如 eth0 网卡对应的行(有可能多行,因为网卡可能使用的是多队列),找出来其中中断次数多的那一列对应的CPU,比如我找到的是:CPU9、CPU21、CPU58、CPU68

然后,我们需要验证,是不是这几个CPU在处理中断,并且繁忙

1
2
3
// 执行 top 命令,然后按1,看一下 si 列的值,是不是被动较大,或者一直比较高
top
// 基本上能看到,就是那几个CPU的si列,一直占用比较高

后续的处理,分2个方向:

①:为硬中断绑定CPU
②:为软中断分散CPU压力

为硬中断绑定CPU(不推荐)

首先,这个操作,并不推荐。因为硬中断和软中断的目的不一样,上边说过了,我们可以先从修改软中断的修改看效果,如果Ok,那就没有必要做硬中断的修改了。最主要的是,修改硬中断,需要停止系统的某个服务,而修改软中断不需要。

下面是修改硬中断的CPU绑定操作

给网卡绑定CPU了,要么手动,要么自动,要么两者结合。之所以这么说,是因为,系统可能存在 irqbalance 服务,这个服务起来后,会自动处理中断和CPU的绑定关系。我们如果手动修改,比较麻烦,手动修改的值,会被 irqbalance 冲掉。所以,我们得关闭这个服务,才能

好了,我们先执行手动操作,执行手动操作之前,先要知道,我们要操作谁。Linux 中,关于中断与CPU亲和性绑定关系的,在 proc/irq/ 目录。每一个子目录,都是一个 中断信号的编号。所以,我们要知道网卡中断对应的编号是什么,并进去修改。所以,第一步,我们如何知道网卡软中断的信号编号是什么

首先,停止 irqbalance 服务

1
systemctl stop irqbalance

然后,查看网卡对应的中断信号

1
2
// 还是这个操作,看统计,找到网卡,最前面的那一列,就是中断信号!
cat /proc/interrupts

比如,我们拿到的是 120、121、122、123 。

然后,修改中断信号和CPU的绑定关系

1
2
3
4
echo 9 > /proc/irq/120/smp_affinity_list
echo 10 > /proc/irq/121/smp_affinity_list
echo 11 > /proc/irq/122/smp_affinity_list
echo 12 > /proc/irq/123/smp_affinity_list
为软中断分散CPU压力

修改硬中断其实并不够彻底,因为硬中的处理虽然分散到了上面几个核,但是硬中断之后,软中断才能开始工作,且,也在同一个核上,一个核即做硬中断处理,也做软中断处理,还是会大大降低硬中断和软中断的处理速度。

通过设置 rps 以及 rfs ,可以将软中断,均匀分配到很多个CPU的核上。

1
2
3
4
5
// 让em1每个队列的处理,均分到32个CPU核上。
echo ffffffff > /sys/class/net/em1/queues/rx-0/rps_cpus
echo ffffffff > /sys/class/net/em1/queues/rx-1/rps_cpus
echo ffffffff > /sys/class/net/em1/queues/rx-2/rps_cpus
echo ffffffff > /sys/class/net/em1/queues/rx-3/rps_cpus

解释:

①:为什么是8个f,一个f,是4个1,1111,8个f,就是32个1,就是代表32个核。
②:为什么是 rx-0 到 rx-4,因为上面说过了,这个网卡,有4个队列。
③:为什么不设置更多个核,是因为设置多了,系统会终止这个操作:“bash: echo: write error: Value too large for defined data type”说明:

收尾

经过上面的操作,网卡丢包问题基本解决,另外,系统网络缓慢问题也解决。

上面的过程,也并非所有的内核参数都尝试做了调整,也有一些并没有调,总结来说,对 netdev_max_backlog 以及 rmem_max 的调整,可以有效的减少丢包。通过调节软中断并对其分散到多个CPU来减少CPU的中断压力,可以有效提高系统网络的RX效率。

另外,前面提到,有些网卡支持 “中断合并” 特性,这个特定对于缓解中断压力有好处,并且默认情况下这个特性是关闭的,对这个服务器的网卡,设置 “中断合并” 操作,并没有生效,设置完成后也不报错,通过 ethtool -C eth0 也依然看到是 Off 状态。

文章参考

Donate comment here