网络调优工具使用

linux内核接收网络包的过程

linux内核对网络包的接收过程大致分为:硬中断处理、接收数据到RingBuffer、ksoftirqd软中断处理几个过程。在软中断处理中,将数据包从RingBuffer中获取送到协议栈进行处理,之后再送到用户进程socket的接收队列中。

几个工具

监控网卡的工具:

ethtool

该工具用来查看和设置网卡参数。这个工具其实本身只是提供几个通用接口,真正的实现是都是在网卡驱动中。几个选项:

  • -i 显示网卡驱动的信息,如驱动的名称、版本等
  • -S 查看网卡收发包的统计情况
  • -g/-G 查看或者修改RingBuffer的大小
  • -l/-L 查看或者修改网卡队列
  • -c/-C 查看或者修改硬中断合并策略
1
2
3
4
5
6
# 查看网卡驱动:
$ethtool -i eno1

driver: e1000e
version: 3.2.6-k
firmware-version: 0.13-4

ifconfig

网络管理工具ifconfig不只是可以为网卡配置ip,启动或者禁用网卡,也包含了一些网卡的统计信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
eno1      Link encap:以太网  硬件地址 98:90:96:a5:4c:a7  
inet 地址:172.27.229.18 广播:172.27.255.255 掩码:255.255.0.0
inet6 地址: fe80::aea1:731a:3687:7f4a/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 跃点数:1
接收数据包:10938370 错误:0 丢弃:0 过载:0 帧数:0
发送数据包:1479049 错误:0 丢弃:0 过载:0 载波:0
碰撞:0 发送队列长度:1000
接收字节:2793457160 (2.7 GB) 发送字节:187860463 (187.8 MB)
中断:20 Memory:f7d00000-f7d20000

中英文对应:
RX packets:接收的总包数
RX bytes:接收的字节数
RX errors:表示总的收包的错误数量
RX dropped:数据包已经进入了 Ring Buffer,但是由于其它原因导致的丢包
RX overruns:表示了 fifo 的 overruns,这是由于 Ring Buffer不足导致的丢包

伪文件系统/proc

Linux内核提供了/proc伪文件系统,通过/proc可以查看内核内部数据结构、改变内核设置

主要内容:

  • /proc/sys目录可以查看或修改内核参数
  • /proc/cpuinfo可以查看CPU信息
  • /proc/meminfo可以查看内存信息
  • /proc/interrupts统计所有的硬中断
  • /proc/softirqs统计的所有的软中断信息
  • /proc/slabinfo统计了内核数据结构的slab内存使用情况
  • /proc/net/dev可以看到一些网卡统计数据

关注伪文件 /proc/net/dev查看内核中对网卡的相关统计,包含的主要信息:

  • bytes: 发送或接收的数据的总字节
  • packets: 接口发送或接收的数据总数
  • errs: 由设备驱动程序检测到的发送或接收错误的总数
  • drop: 设备驱动程序丢弃的数据包总数
  • fifo: FIFO缓冲区错误的数量
  • frame: The number of packet framing errors.(分组帧错误的数量)
  • colls: 接口上检测到的冲突数

伪文件系统sysfs

sysfs/proc类似,也是一个伪文件系统,但是比proc更新,结构更清晰。其中的/sys/class/net/eno1/statistics/也包含了网卡的统计信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
# cd /sys/class/net/eno1/statistics/ 
# grep . * | grep tx

tx_aborted_errors:0
tx_bytes:193280753
tx_carrier_errors:0
tx_compressed:0
tx_dropped:0
tx_errors:0
tx_fifo_errors:0
tx_heartbeat_errors:0
tx_packets:1513872
tx_window_errors:0

RingBuffer监控和调优

当网线中的数据帧到达网卡后,第一站就是RingBuffer(网卡通过DMA机制将数据帧送到RingBuffer中)。

使用ethtool查看RingBuffer:

1
2
3
4
5
6
7
8
9
10
11
12
13
# ethtool -g eno1

Ring parameters for eno1:
Pre-set maximums:
RX: 4096
RX Mini: 0
RX Jumbo: 0
TX: 4096
Current hardware settings:
RX: 256
RX Mini: 0
RX Jumbo: 0
TX: 256

本机器的网卡设置RingBuffer最大允许到4096,实际设置为256。

注意:ethtool查看到的是实际是Rx bd的大小。Rx bd位于网卡中,相当于一个指针。RingBuffer在内存中,Rx bd指向RingBufferRx bdRingBuffer中的元素是一一对应的关系。在网卡启动的时候,内核会为网卡的Rx bd在内存中分配RingBuffer,并设置好对应关系。

在Linux的整个网络栈中,RingBuffer起到一个任务的收发中转站的角色。对于接收过程来讲,网卡负责往RingBuffer中写入收到的数据帧,ksoftirqd内核线程负责从中取走处理。只要ksoftirqd线程工作的足够快,RingBuffer这个中转站就不会出现问题。但是设想一下,假如某一时刻,瞬间来了特别多的包,而ksoftirqd处理不过来了,会发生什么?这时RingBuffer可能瞬间就被填满了,后面再来的包网卡直接就会丢弃,不做任何处理!

查看机器上是否有因为RingBuffer设置导致的丢包:

1
2
3
4
# ethtool -S eno1

rx_fifo_errors: 0 #有的机器上不一定有
tx_fifo_errors: 0

rx_fifo_errors如果不为0的话(在 ifconfig中体现为overruns 指标增长),就表示有包因为RingBuffer装不下而被丢弃了。那么怎么解决这个问题呢?很自然首先想到的是,加大RingBuffer这个“中转仓库”的大小。通过ethtool就可以修改:

1
# ethtool -G eth1 rx 512 tx 512

这样网卡会被分配更大一点的”中转站“,可以解决偶发的瞬时的丢包。不过这种方法有个小副作用,那就是排队的包过多会增加处理网络包的延时。所以另外一种解决思路更好,那就是让内核处理网络包的速度更快一些,而不是让网络包傻傻地在RingBuffer中排队。怎么加快内核消费RingBuffer中任务的速度?

硬中断监控与调优

监控部分

硬中断可以通过内核提供的伪文件/proc/interrupts来查看:

1
2
3
4
$ cat /proc/interrupts

CPU0 CPU1 CPU2 CPU3 CPU4 CPU5 CPU6 CPU7
31: 0 0 0 0 0 536 0 4507261 IR-PCI-MSI 409600-edge eno1

分析:

  • 网卡的输入队列eno1的中断号是31
  • 31号中断由CPU5和CPU7来处理

注意:

  • 为什么输入队列的中断在CPU5和CPU7上?

这是因为内核的一个配置,在伪文件系统中可以查看到:

1
2
#cat /proc/irq/31/smp_affinity
80

smp_affinity里是CPU的亲和性的绑定,80是二进制的01010000,第5位和第7位都为1,代表的就是第5和第7个CPU核心CPU5和CPU7。

  • 对于收包来过程来讲,硬中断的总次数表示的是Linux收包总数吗?

不是,硬件中断次数不代表总的网络包数。第一网卡可以设置中断合并,多个网络帧可以只发起一次中断。第二NAPI 运行的时候会关闭硬中断,通过poll来收包。

多队列网络调优

现在的主流网卡基本上都是支持多队列的,可以通过将不同的队列分给不同的CPU核心来处理,从而加快Linux内核处理网络包的速度。这是最为有用的一个优化手段

每一个队列都有一个中断号,可以独立向某个CPU核心发起硬中断请求,让CPU来poll包。通过将接收进来的包被放到不同的内存队列里,多个CPU就可以同时分别向不同的队列发起消费了。这个特性叫做RSS(Receive Side Scaling,接收端扩展)。通过ethtool工具可以查看网卡的队列情况。

1
2
3
4
5
6
7
8
9
10
11
12
# ethtool -l eth0 # 本人网卡不支持该操作,参考开发内功修炼
Channel parameters for eth0:
Pre-set maximums:
RX: 0
TX: 0
Other: 1
Combined: 63
Current hardware settings:
RX: 0
TX: 0
Other: 1
Combined: 8

上述结果表示当前网卡支持的最大队列数是63,当前开启的队列数是8。对于这个配置来讲,最多同时可以有8个核心来参与网络收包。如果想提高内核收包的能力,直接简单加大队列数就可以了,这比加大RingBuffer更为有用。因为加大RingBuffer只是给个更大的空间让网络帧能继续排队,而加大队列数则能让包更早地被内核处理ethtool修改队列数量方法如下:

1
#ethtool -L eth0 combined 32

硬中断发生在哪一个核上,它发出的软中断就由哪个核来处理。所有通过加大网卡队列数,这样硬中断工作、软中断工作都会有更多的核心参与进来。

每一个队列都有一个中断号,每一个中断号都是绑定在特定的CPU上。如果不满意某一个中断的CPU绑定,可以通过修改/proc/irq/{中断号}/smp_affinity来实现。

硬中断合并

一个实际中的例子,假如你是一位开发同学,和你对口的产品经理一天有10个小需求需要让你帮忙来处理。她对你有两种中断方式:

  • 第一种:产品经理想到一个需求,就过来找你,和你描述需求细节,然后让你帮你来改
  • 第二种:产品经理想到需求后,不来打扰你,等攒够5个来找你一次,你集中处理

现在不考虑及时性,只考虑工作整体效率,哪种方案下你的工作效率会高呢?或者换句话说,你更喜欢哪一种工作状态呢?很明显,只要你是一个正常的开发,都会觉得第二种方案更好。对人脑来讲,频繁的中断会打乱你的计划,你脑子里刚才刚想到一半技术方案可能也就废了。当产品经理走了以后,你再想捡起来刚被中断之的工作的时候,很可能得花点时间回忆一会儿才能继续工作。

对于CPU来讲也是一样,CPU要做一件新的事情之前,要加载该进程的地址空间,load进程代码,读取进程数据,各级别cache要慢慢热身。因此如果能适当降低中断的频率,多攒几个包一起发出中断,对提升CPU的工作效率是有帮助的。所以,网卡允许我们对硬中断进行合并。

现在看一下网卡的硬中断合并配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
# ethtool -c eno1

Adaptive RX: off TX: off
...
rx-usecs: 3
rx-frames: 0
rx-usecs-irq: 0
rx-frames-irq: 0
rx-usecs-low: 0
rx-frame-low: 0
rx-usecs-high: 0
rx-frame-high: 0
...

参数的含义:

  • Adaptive RX: 自适应中断合并,网卡驱动自己判断啥时候该合并啥时候不合并
  • rx-usecs:当过这么长时间过后,一个RX interrupt就会被产生
  • rx-frames:当累计接收到这么多个帧后,一个RX interrupt就会被产生

修改相关参数:接使用ethtool -C就可以

1
# ethtool -C eno1 adaptive-rx on

需要注意的是,减少中断数量虽然能使得Linux整体吞吐更高,不过一些包的延迟也会增大,所以用的时候得适当注意。

软中断监控和调优

软中断和它对应的硬中断是在同一个核心上处理的。因此,前面硬中断分散到多核上处理的时候,软中断的优化其实也就跟着做了,也会被多核处理。不过软中断也还有自己的可优化选项。

监控

软中断的信息可以从/proc/softirqs读取:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ cat /proc/softirqs

CPU0 CPU1 CPU2 CPU3 CPU4 CPU5 CPU6 CPU7
HI: 0 0 1228152 865 360 7 0 0
TIMER: 27307279 27853788 27665421 31261338 27720654 27228264 27604399 29304702
NET_TX: 10611 21 33099 1480 103 39090 43 56
NET_RX: 936563 934954 932782 903383 896445 940357 1111527 11896810
BLOCK: 293 1850485 65944 395 365 332 354 333
IRQ_POLL: 0 0 0 0 0 0 0 0
TASKLET: 18 4 104755 75 25 84 748 8691
SCHED: 15801149 13822836 13336518 16738535 13374504 13423444 13224695 14819090
HRTIMER: 0 0 0 0 0 0 0 0
RCU: 23189803 22923131 23217378 25381785 22908760 22872174 23143036 24117847
软中断budget调整

番茄工作法:大致意思就是要有一整段的不被打扰的时间,集中精力处理某一项作业。这一整段时间时长被建议是25分钟。对于Linux的处理软中断的ksoftirqd来说,它也和番茄工作法思路类似。一旦它被硬中断触发开始了工作,它会集中精力处理一波网络包(绝不只是1个),然后再去做别的事情。

处理一波是多少呢,策略略复杂。只说其中一个比较容易理解的,那就是net.core.netdev_budget内核参数:

1
2
3
# sysctl -a | grep core.netdev_budget 

net.core.netdev_budget = 300

这里的意思说的是,ksoftirqd一次最多处理300个包,处理够了就会把CPU主动让出来,以便Linux上其它的任务可以得到处理。那么假如说,现在想提高内核处理网络包的效率。那就可以让ksoftirqd进程多干一会儿网络包的接收,再让出CPU。至于怎么提高,直接修改不这个参数的值就行。

1
# sysctl -w net.core.netdev_budget=600

如果要保证重启仍然生效,需要将这个配置写到/etc/sysctl.conf

软中断GRO合并

GRO和硬中断合并的思想很类似,不过阶段不同。硬中断合并是在中断发起之前,而GRO已经到了软中断上下文中了。

如果应用中是大文件的传输,大部分包都是一段数据,不用GRO的话,会每次都将一个小包传送到协议栈(IP接收函数、TCP接收)函数中进行处理。开启GRO的话,Linux就会智能进行包的合并,之后将一个大包传给协议处理函数。这样CPU的效率也是就提高了。

1
2
# ethtool -k eno1 | grep generic-receive-offload
generic-receive-offload: on

如果网卡驱动没有打开GRO的话,可以通过如下方式打开。

1
# ethtool -K eno1  gro on

GRO说的仅仅只是包的接收阶段的优化方式,对于发送来说是GSO。

参考

https://mp.weixin.qq.com/s?__biz=MjM5Njg5NDgwNA==&mid=2247484065&idx=1&sn=ab0d3e11c472b845dedf6a87dcc38b25&chksm=a6e3039a91948a8c23ec7d426469ebe30d70f59c7a58437e9ea8b8aac697aa1e2e29b4c47dec&scene=178&cur_album_id=1532487451997454337#rd