引言

在运维过程中,出现网络问题是非常棘手的,当访问某服务出现时通时不通的情况时,我们应该如何排查?是不是网卡配置有问题?是不是内核参数有问题?是多网卡吗?有没有做bond?复杂的网络环境经常搞得人晕头转向,本文就列举笔者运维中遇到过的典型的的网络问题现象,来记录一下其排查的思路和步骤。

问题现象

服务器丢包、网络时断时续、Ping时响应时间忽快忽慢、网卡接收流量正常返回流量失败等网络问题,都可以使用以下通用的排查步骤,再结合自身环境的情况,逐一排查怀疑点,最终定位问题所在。

排查工具及案例分析

我们首先搞清楚数据包从网卡接收,一直到应用程序收到,其中间都发生了什么?

基本流程如下:

数据包进入网卡 --> 网卡硬件缓存FIFO --> 驱动缓冲区(Ring Buffer) ---> 协议栈处理缓冲区(sk_buff) --> 传输层缓冲区(TCP/UDP接收窗口) --> Socket接收队列 --> 用户态应用缓冲(应用程序)

大致流程图如下:

数据到达网卡后,会先存在网卡硬件缓存FIFO,然后通过DMA传输到Ring Buffer指向的skb数据缓冲区(用于存储网络数据包的结构体,包含了数据包的内容和元信息),也就是sk_buff。之后触发cpu硬中断。然后是协议栈(TCP/IP)的处理,内核从Ring Buffer中取出skb,调整指针,发送至传输层缓冲区。然后发送至socket接收缓冲区。最后用户态的应用程序调用recv()将数据包取出。

排查工具介绍

网卡层面排查工具

  1. 通过ifconfig查看

    # ifconfig em1
    em1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
    inet 172.30.210.199 netmask 255.255.255.0 broadcast 172.30.210.255
    inet6 fe80::7693:5725:5504:e974 prefixlen 64 scopeid 0x20<link>
    ether 1c:83:41:93:20:46 txqueuelen 1000 (Ethernet)
    RX packets 271922 bytes 29995005 (28.6 MiB)
    RX errors 0 dropped 3396 overruns 0 frame 0
    TX packets 325387 bytes 132126535 (126.0 MiB)
    TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
    device interrupt 94

    RX(receive)表示接收报文,TX(transmit)表示发送报文。

    对于接收数据,重点需要关注errors、dropped和overruns三个参数值的大小:

    RX errors:表示总的收包的错误数量,这包括too-long-frames错误,Ring Buffer溢出错误,crc校验错误,帧同步错误,FIFO overruns以及missed pkg等等。

    RX dropped:表示数据包已经进入了Ring Buffer,但是由于内存不够等系统原因,导致在拷贝到内存的过程中被丢弃。

    RX overruns:表示了fifo的overruns,这是由于Ring Buffer(aka Driver Queue)传输的IO大于kernel能够处理的IO导致的,overruns的增大意味着数据包没到Ring Buffer就被网卡物理层给丢弃了,而CPU无法及时处理中断是造成Ring Buffer满的原因之一

  2. 查看/proc/net/dev文件

    # cat /proc/net/dev
    Inter-| Receive | Transmit
    face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed
    lo: 176076 472 0 0 0 0 0 0 176076 472 0 0 0 0 0 0
    em1: 30638495 276730 0 3620 0 0 0 49 132188337 326011 0 0 0 0 0 0
    docker0: 2367947 283 0 0 0 0 0 0 65366 393 0 0 0 0 0 0

    同样需要特别关注errs、drop和fifo的值,其对应了ifconfig的errors、dropped和overruns,其中drop和fifo的最大区别是:

    drop:表示这个数据包已经进入到网卡的接收缓存fifo队列,并且开始被系统中断处理准备进行数据包拷贝(从网卡缓存fifo队列拷贝到系统内存),但由于此时的系统原因(比如内存不够),导致这个数据包被丢掉。

    fifo:表示这个数据包还没被进入到网卡的接收缓存fifo队列就被丢掉,因此,此时的网卡fifo是满的。至于为什么是满的,可能是系统繁忙,来不及响应网卡中断,导致网卡里数据包没有及时拷贝到系统内存,所以在遇到此情况时,可以检测cpu负载与cpu中断情况。

  3. ethtool工具查看网卡数据

    # ethtool -S em1 | grep -iE 'error|drop'
    tx_underflow_errors: 0
    tx_carrier_error_frames: 0
    tx_excessive_deferral_error: 0
    rx_crc_errors: 0
    rx_align_error: 0
    rx_crc_errors_small_packets: 0
    rx_crc_errors_giant_packets: 0
    rx_length_errors: 0
    rx_out_of_range_errors: 0
    rx_fifo_overflow_errors: 0
    rx_watchdog_errors: 0
    rx_receive_error_frames: 0
    fatal_bus_error: 0

    主要观察rx_length_errors、rx_out_of_range_errors和rx_fifo_overflow_errors参数

  4. netstat工具

    netstat查看网卡的收发包以及丢包情况:

    # netstat -i
    Kernel Interface table
    Iface MTU RX-OK RX-ERR RX-DRP RX-OVR TX-OK TX-ERR TX-DRP TX-OVR Flg
    docker0 1500 786339 0 0 0 1440038 0 0 0 BMRU
    em1 1500 3999545 18446744073709551615 4805 0 2278246 0 0 0 BMRU
    lo 65536 774 0 0 0 774 0 0 0 LRU

    netstat按照协议分类统计:

    # netstat -s

内核层面排查工具

  1. ss 和 netstat

    ss 和 netstat类似,都可以获取当前系统的网络状态,常用的参数有:

    查看所有监听状态(tcp/udp)套接字:

    # ss -tnlp
    # ss -unlp State Recv-Q Send-Q Local Address:Port Peer Address:Port Process

    显示所有套接字:

    # ss -an
    Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port # netstat -anp
    Active Internet connections (servers and established)
    Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name Active UNIX domain sockets (servers and established)
    Proto RefCnt Flags Type State I-Node PID/Program name Path

    Linux netstat命令结果分为两部分:

    • Active lnternet connections(有源Internet连接):用于网络连接传输

    • Active UNIX domain sockets(有源UNIX套接字):只能用于本地通信,性能比第一种好

    Active Internet connections 部分:

    • Proto:通过此字段可以看到连接使用的是什么协议,主要是TCP、UDP,还有TCP6、UDP6这就是使用了ipv6的协议
    • Recv-Q(非LISTEN状态下):表示收到的数据已经在本地接收缓冲,但是还有多少没有被进程取走,recv()。如果接收队列Recv-Q一直处于阻塞状态,也就是Recv-Q值不为零并且值挺大,可能是遭受了Dos 攻击。
    • Send-Q(非LISTEN状态下):对方没有接收的数据,仍然在本地缓冲区中,或者说没有收到对方的Ack。如果发送队列Send-Q不能很快的清零,可能是有应用向外发送数据包过快,或者是对方接收数据包不够快。这时候就要调整发送速度或者接收速度了。例如:如果看到是大量的send-Q,可以判定是发送数据给目的地址的时候出现了阻塞的问题,导致了包堆积在本地缓存中,不能成功发出去。那么问题就可能产生在了客户端,根据业务逻辑可以看看是不是客户端发送的TCP长连接数量过多。验证办法,尝试减少客户端和服务的长连接,查看效果。
    • Local Address:本地地址和端口
    • Foreign Address/Peer Address:Port:远端地址和端口
    • State:标识tcp连接状态
    • PID/Program name:使用该连接的进程id和name

    为什么Recv-Q和Send-Q标注了为非LISTEN状态下?因为在LISTEN状态下:

    • Recv-Q表示的当前等待服务端调用 accept 完成三次握手的 listen backlog数值,也就是说,当客户端通过 connect()去连接在listen()的服务端时,这些连接会-直处于Recv-Q这个queue里面直到被服务端accept()

    • Send-Q表示的则是最大的 listen bäcklog 数值。

    Active UNIX domain sockets部分:

    • RefCnt:引用计数(即通过此套接字附加的进程),也就是连接到本套接口上的进程数量

    • Flags:SO_ACCECPTON进程正在等待连接请求还未连接的套接字

    • Type:套接字类型有SOCK_DGRAM数据报(无连接)模式、SOCK_STREAM流(连接)套接字、SOCK_RAW原始套接字、SOCK_RDM这个服务器提供可靠传递的消息、SOCK_SEOPACKET一个顺序数据包套接字和SOCK_PACKET原始接口访问套接字

    • State:套接字当前的状态。包括FREE分配套接字、LISTENING套接字正在侦听连接请求、CONNECTING套接字即将建立连接、CONNECTED已连接套接字、DISCONNECTING套接字正在断开连接和empty套接字未连接到另一个套接字

    • I-Node:文件的Inode节点

    • Path:附加到套接字的相应进程的路径名

  2. sysctl:内核参数查看及修改命令

  3. perf+ftrace:内核性能分析工具

    场景 命令示例
    实时监控中断 sudo perf top -e irq:irq_handler_entry
    统计中断事件 sudo perf stat -e irq:* -a sleep 10
    记录调用栈 sudo perf record -e irq:irq_handler_entry -g -a -- sleep 30
    生成火焰图 perf script
    系统中断计数器 watch -n 1 "cat /proc/interrupts"

案例分析

ping延时问题分析

问题现象

压力测试场景下,无压力测试时,网络时延正常,有压力测试时,网络时延不稳定,体现在ping时延时较高。

排查思路

查看CPU情况

执行lscpu查看CPU基本情况,核心数、numa节点数等,例如:

# lscpu
架构: aarch64
CPU 运行模式: 64-bit
字节序: Little Endian
CPU: 128
在线 CPU 列表: 0-127
每个核的线程数: 1
每个座的核数: 64
座: 2
NUMA 节点: 4
厂商 ID: HiSilicon
BIOS Vendor ID: HiSilicon
型号: 0
型号名称: Kunpeng-920
BIOS Model name: HUAWEI Kunpeng 920 7260
步进: 0x1
Frequency boost: disabled
CPU 最大 MHz: 2600.0000
CPU 最小 MHz: 200.0000
BogoMIPS: 200.00
L1d 缓存: 8 MiB
L1i 缓存: 8 MiB
L2 缓存: 64 MiB
L3 缓存: 256 MiB
NUMA 节点0 CPU: 0-31
NUMA 节点1 CPU: 32-63
NUMA 节点2 CPU: 64-95
NUMA 节点3 CPU: 96-127
Vulnerability Gather data sampling: Not affected
Vulnerability Itlb multihit: Not affected
Vulnerability L1tf: Not affected
Vulnerability Mds: Not affected
Vulnerability Meltdown: Not affected
Vulnerability Mmio stale data: Not affected
Vulnerability Retbleed: Not affected
Vulnerability Spec store bypass: Not affected
Vulnerability Spectre v1: Mitigation; __user pointer sanitization
Vulnerability Spectre v2: Not affected
Vulnerability Srbds: Not affected
Vulnerability Tsx async abort: Not affected
标记: fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma
dcpop asimddp asimdfhm

可见cpu为128核心,NUMA节点有四个。

查看网卡绑定在那个NUMA上
# cat /sys/class/net/enp125s0f0/device/numa_node
0

所以网卡enp125s0f0在numa node 0上的cpu中运行,性能更好

查看网卡的亲和性CPU
cat /sys/class/net/enp125s0f0/device/local_cpulist
0-31

可见更倾向于使用CPU 0-31

查看CPU使用率、负载情况

使用top、ps等命令,确认负载情况

查看网卡队列

网卡多队列技术是指将各个队列通过中断绑定到不同的核上,从而解决网络I/0带宽升高时单核CPU的处理瓶颈,提升网络PPS和带宽性能。在相同的网络PPS和网络带宽的条件下,与1个队列相比,2个队列最多可提升性能达50%到100%,4个队列的性能提升更大。

# ethtool -l enp125s0f1
Channel parameters for enp125s0f1:
Pre-set maximums:
RX: 0
TX: 0
Other: 1
Combined: 1
Current hardware settings:
RX: 0
TX: 0
Other: 1
Combined: 1

这里可以看到,其网卡队列只有一个,这里有两种情况:

1是此网卡的确是单队列网卡;2是此网卡是多队列网卡,但由于机器启动时BIOS中NIC的配置里,只配置了1个队列。

如何判断网卡是否支持多队列呢?按照以下步骤排查:

(1)查看网卡的bus-info

# ethtool -i enp125s0f0
driver: hns3
version: 4.19.90-89.21.v2401.ky10.aarch6
firmware-version: 1.9.40.6
expansion-rom-version:
bus-info: 0000:7d:00.0
supports-statistics: yes
supports-test: yes
supports-eeprom-access: no
supports-register-dump: yes
supports-priv-flags: yes

(2)查看网卡是否支持多队列

# lspci -vvv | grep -A 80 '7d:00.0' | grep -i msi-x
Capabilities: [a0] MSI-X: Enable+ Count=131 Masked-

此回显表示,此网卡支持多队列,且最高支持131队列。但为什么只显示为1个队列呢?很有可能是在BIOS中的配置引起的。

查看Ring Buffer大小
# ethtool -g enp125s0f0
Ring parameters for enp125s0f0:
Pre-set maximums:
RX: 32760 # 最大支持 32760
RX Mini: 0
RX Jumbo: 0
TX: 32760 # 最大支持 32760
Current hardware settings:
RX: 1024 # 目前设置为 1024
RX Mini: 0
RX Jumbo: 0
TX: 1024 # 目前设置为 1024
以上基本的配置确认后,下一步查看丢包情况
# ifconfig enp125s0f0

此案例ping时仅有延迟,无丢包情况,所以把重点放在cpu资源、中断的情况上。

分析网卡中断情况
# grep hns3-0000:7d:00.0 /proc/interrupts
263: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 781452 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ITS-MSI 65536001 Edge hns3-0000:7d:00.0-TxRx-0

其中hns3为网卡的driver名称,0000:7d:00.0为网卡的bus-info。可以使用ethtool -i enp125s0f0查看

使用以上命令确认处理中断的cpu是第几个核心,例如(以下只是讲述如何判断处理中断发生在哪个核心cpu,与本次问题处理无关):

# cat /proc/interrupts
CPU0 CPU1 CPU2 CPU3
0: 3710374484 0 0 0 IO-APIC-edge timer
1: 20 0 0 0 IO-APIC-edge i8042
6: 5 0 0 0 IO-APIC-edge floppy
7: 0 0 0 0 IO-APIC-edge parport0
8: 0 0 0 0 IO-APIC-edge rtc
9: 0 0 0 0 IO-APIC-level acpi
12: 240 0 0 0 IO-APIC-edge i8042
14: 11200026 0 0 0 IO-APIC-edge ide0
67: 19386473 0 0 0 IO-APIC-level eth0
NMI: 0 0 0 0 Non-maskable interrupts
LOC: 3737150067 3737142382 3737145101 3737144204 Local timer interrupts
ERR: 0
MIS: 0
  • 第一列为如 0167 等,表示硬件设备的中断请求编号

  • 与CPU对应的每列数字表示对应 CPU 核心处理该中断的总次数。例如, 3710374484`表示 CPU0 处理了 IRQ0 中断超过 37 亿次

  • 倒数第二列为中断类型标志

  • 最后一列代表关联设备/驱动名称

通过上述方式再结合实际信息可以确认,网卡enp125s0f0设备的硬中断处理发生在CPU19中。

扩展:

还可以通过cat /proc/irq/263/smp_affinity来确认263编号的设备,其进行中断的cpu是第几个,例如:

cat /proc/irq/263/smp_affinity
00000000,00000000,00000000,00080000

这里展示的是十六进制的写法,00080000等同于0x00080000

首先需要将十六进制转为二进制,十六进制中每一位数对应二进制的四位数(一个十六进制位代表四个比特位),前边的三段全为0,所以不用计数,只需计算第四段的00080000,换算为:

十六进制位:  0    0    0    8    0    0    0    0
二进制位: 0000 0000 0000 1000 0000 0000 0000 0000

二进制1000表示:1 * 2^3 + 0 * 2^2 + 0 * 2^1 + 0 * 2^0 = 8

转换为二进制位后,从右往左数表示从CPU0开始,一直到CPUx。此时1在从右往左数的第20位,故其中断的CPU为CPU19。

知道这个规律后,就可以直接使用echo xx > /proc/irq/67/smp_affinity来修改中断的CPU核心。

解决思路

此问题现象是ping时有延时,未涉及TCP及应用,则在此不考虑tcp缓冲区以及socket缓冲区满的情况,也未发现丢包情况,故很大可能性为中断CPU资源不足,或内核进程调度延迟导致网卡驱动收包延迟,再针对性的进行排查。

CPU资源不足排查

通过上述排查步骤,可以确认网卡队列最大可支持131个,但仅开启了一个,且绑定的中断CPU为CPU19。

此时可通过sosreport或其他监控服务来确认CPU19是否存在长时间使用率较高的情况,若存在,则表示频繁的中断导致CPU19较为繁忙,中断处理不及时,最终导致ping时偶发延时高的现象。

这时就要开启网卡的多队列,使中断分布到多个CPU下,分散单个CPU的处理压力,本示例为鲲鹏920服务器,具体调整多队列操作如下:

进入BIOS --> Advanced --> Lom Configuration --> NIC Configuration --> Port1 Configuration --> 修改Tqp Number为60 --> 修改Function Number为1 --> 保存退出

此时再查看网卡队列数:

# ethtool -l enp125s0f0
Channel parameters for enp125s0f0:
Pre-set maximums:
RX: 0
TX: 0
Other: 1
Combined: 60
Current hardware settings:
RX: 0
TX: 0
Other: 1
Combined: 60

再次查看中断分布情况:

# grep hns3-0000:7d:00.0 /proc/interrupts | wc -l
60

已经是60个队列了,再结合前边网卡的亲和性CPU配置:

# cat /sys/class/net/enp125s0f0/device/local_cpulist
0-31

此网卡更亲和于NUMA 0节点第0-31个CPU,所以我们可以先将队列改为32个,使其分布到0-31个cpu核中:

# ethtool -L enp125s0f0 combined 32
# ethtool -l enp125s0f0
Channel parameters for enp125s0f0:
Pre-set maximums:
RX: 0
TX: 0
Other: 1
Combined: 60
Current hardware settings:
RX: 0
TX: 0
Other: 1
Combined: 32 # grep hns3-0000:7d:00.0 /proc/interrupts | wc -l
32

随后还可以通过cat /proc/irq/<设备编号>/smp_affinity查看其32个队列分别绑定哪个cpu核,也可以通过echo "5" > /proc/irq/268/smp_affinity来手动指定某个队列绑定哪个cpu核,用来实现网卡中断的效率最大化。

内核进程调度延迟排查

若经排查发现CPU19使用率正常,此时可怀疑是否为某些进程占有了CPU资源或者软锁不可抢占,导致内核调度被阻塞,最终导致的ping延迟,且收到包的时候统一往后推延。

此时我们需要使用perf等内核性能分析工具来进行排查诊断:

# 捕捉并记录与进程/线程调度相关的性能事件,-T(任务生命周期跟踪),sleep 10(持续记录10s)
perf sched record -aT sleep 10

再分析生成的perf.data:

# 分析任务的平均/最大调度延迟
perf sched latency -s max

若内核线程包括软中断(ksoftirqd)都存在较大的调度延迟,例如2s,这说明从挂起软中断,要经历2s多的等待,软中断才得以执行。网卡的收包流程中,当网卡硬件收到报文时,先触发硬中断,在硬中断中触发软中断(NET_RX_SOFTIRQ),实际的收包程序是在软中断中完成的。而此场景中软中断延迟2s,最终导致了ping延迟的现象。

内核进程调度延迟的根本原因,大多是某个进程占有了CPU资源或者软锁且不可抢占导致内核调度被阻塞住了。需要结合实际的业务场景,抓取火焰图,甚至再结合内核源码,进一步定位是什么进程导致。

丢包问题分析

问题现象

服务器网卡存在发收包有丢包情况,或用户应用存在发收包丢包情况。

排查思路

文章开篇已经对ifconfigethtoolcat /proc/net/devss等命令进行解释,并说明了如何判断丢包产生在哪个环节,再针对具体的环节进行排查和优化。

解决思路

以下列举并分析几种常见的丢包场景,以及排查的思路。

Ring Buffer溢出

Ring Buffer的空间是有限的,当收到的数据包速率大于单个CPU处理速度的时候,Ring Buffer就可能被占满,占满后再进来的数据包,将会被丢弃。

  1. 中断能力不足导致的Ring Buffer占满

    如果网卡为单队列,网络数据包接收的速率又非常高,就有可能导致CPU中断不及时,数据包处理缓慢,最终导致Ring Buffer占满,新数据包被丢弃。

    这个就要根据上文中“CPU资源不足排查”步骤,来将网卡修改为多队列,增快CPU处理中断及数据包的速度,来避免此问题。

  2. RingBuffer配置过小,或内核处理网络数据较慢

    当RingBuffer配置过小,或内核处理网络数据较慢时,会导致RingBuffer拥堵,可以调大RingBuffer的容量。

    查看当前容量设置及最大支持容量:

    # ethtool -g enp125s0f0
    Ring parameters for enp125s0f0:
    Pre-set maximums:
    RX: 32760 # 最大支持 32760
    RX Mini: 0
    RX Jumbo: 0
    TX: 32760 # 最大支持 32760
    Current hardware settings:
    RX: 1024 # 目前设置为 1024
    RX Mini: 0
    RX Jumbo: 0
    TX: 1024 # 目前设置为 1024

    最大支持32760,而目前是1024,可适量调大其值:

    # ethtool -G enp125s0f0 rx 4096 tx 4096
    # ethtool -g enp125s0f0
    Ring parameters for enp125s0f0:
    Pre-set maximums:
    RX: 32760
    RX Mini: 0
    RX Jumbo: 0
    TX: 32760
    Current hardware settings:
    RX: 4096
    RX Mini: 0
    RX Jumbo: 0
    TX: 4096
netdev_max_backlog溢出

cat /proc/net/softnet_stat文件是记录每个CPU的软中断处理情况。

# cat /proc/net/softnet_stat
00120244 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
0013df4c 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
001458ef 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00cda99f 00000000 0000000a 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000

它共有10列,每列都是16进制的数字,分别表示:

第一列:processed表示处理了多少个数据包

第二列:dropped表示丢弃了多少个数据包,因为队列满了或者内存不足

第三列:time_squeeze表示发生了多少次时间压缩,即软中断处理时间超过了预设的限制

第四列:cpu_collision表示发生了多少次CPU碰撞,即同一个数据包被多个CPU同时处理

第五列:received_rps表示接收到了多少个RPS(接收包转发)的数据包,即数据包被转发到了另一个CPU处理

第六列:flow_limit_count表示发生了多少次流量限制,即数据包被丢弃,因为超过了RPS的限制。

第七列:backlog_limit_count表示发生了多少次积压限制,即数据包被丢弃,因为超过了软中断队列的限制。

第八列:droppe_no_skbuf表示丢弃了多少个数据包,因为没有足够的sk_buff结构体来存储它们

第九列:no_cpu_resources表示丢弃了多少个数据包,因为没有足够的CPU资源来处理它们

第十列:reserved保留未用。

其中,第二列即代表由于netdev_max_backlog队列溢出而丢弃的包总数,若其不为0,可以通过修改增大net.core.netdev_max_backlog来尝试解决。

查看netdev_max_backlog参数是否过小:

# sysctl -a | grep net.core.netdev_max_backlog
net.core.netdev_max_backlog = 8000

net.core.netdev_max_backlog用于控制网络接口接收数据包时的队列容量,如超过该阈值,新包将会被丢弃,可以适当调大。

net.core.netdev_budget导致丢包

/proc/net/softnet_stat文件第三列如果一直增加的话,表示SoftIRQ获取的CPU时间太短,来不及处理足够多的网络包,那么就需要减小netdev_budget的值,一直到不再增加。

查看net.core.netdev_budget参数数值:

# sysctl -a | grep net.core.netdev_budget
net.core.netdev_budget = 1024
net.core.netdev_budget_usecs = 2000

net.core.netdev_budget用于控制单次软中断(SoftIRQ)处理的数据包数量,低延迟敏感场景通常会降低其值,缩短软中断执行时间。高吞吐场景或dropped包持续增大时,可以适当增大其值。

netdev_budget_usecs表示单次软中断的最大处理时间(单位:微秒),避免软中断过度使用CPU

net.core.netdev_budget如果设置太小,软中断就会变多,软中断变多,就会导致sys上升,产生额外的cpu开销,导致load上升,load上升会影响处理包的个数会越来越少。

TCP/UDP/Socket Buffer溢出

Socket可以屏蔽Linux内核不同协议的差异,为应用程序提供统一的访问接口。每个Socket都有一个读写缓冲区。

  • 读缓冲区:缓存远端发来的数据,如果读缓冲区已满,就不能再接收新的数据。

  • 写缓冲区:缓存要发出去的数据,如果写缓冲区已满,应用程序的写操作就会堵塞。

内核参数配置如下:

# sysctl -a | grep mem
net.core.optmem_max = 524288
net.core.rmem_default = 2097152
net.core.rmem_max = 16777216
net.core.wmem_default = 2097152
net.core.wmem_max = 2097152
net.ipv4.tcp_mem = 180486 240648 360972
net.ipv4.tcp_rmem = 4096 131072 6291456
net.ipv4.tcp_wmem = 4096 16384 4194304
net.ipv4.udp_mem = 360972 481297 721944
net.ipv4.udp_rmem_min = 4096
net.ipv4.udp_wmem_min = 4096
参数 含义
net.core.optmem_max 单个套接字辅助缓冲区的最大值,控制元数据存储
net.core.rmem_default 全局的套接字接收缓冲区默认初始大小
net.core.rmem_max 全局的套接字接收缓冲区最大允许值
net.core.wmem_default 全局的套接字写入缓冲区默认初始大小
net.core.wmem_max 全局的套接字写入缓冲区最大允许值
net.ipv4.tcp_mem 整个TCP协议栈的内存使用,三个值 min pressure max
net.ipv4.tcp_rmem 单个 TCP 连接的接收缓冲区大小,三个值 min default max
net.ipv4.tcp_wmem 单个 TCP 连接的写入缓冲区大小,三个值 min default max
net.ipv4.udp_mem 所有UDP套接字的内存总量,三个值 min pressure max
net.ipv4.udp_rmem_min 每个 UDP 套接字接收缓冲区的最小值
net.ipv4.udp_wmem_min 每个 UDP 套接字写入缓冲区的最小值

tcp buffer溢出:

tcp缓冲区不存在溢出丢包的情况,tcp有流量控制策略,根据tcp的流量控制中的滑动窗口机制,接收方会将窗口大小给到发送方,发送方再根据窗口大小来发送数据。

如果tcp socket buffer满的话,抓包时可以体现,会出现window zero的提示,这时就表示tcp buffer满了,通知发送方数据发慢一点,所以有时tcp传输非常慢时,也有可能是tcp buffer满了。

udp buffer溢出:

通过/proc/net/udp文件可以监控udp socket的统计信息:

# cat /proc/net/udp
sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode ref pointer drops
19338: 0100007F:0143 00000000:0000 07 00000000:00000000 00:00000000 00000000 0 0 93274 2 000000001c69a696 0

重点关注tx_queue、rx_queue和drops

drops的数值一直增长的话,就是丢包了(仅代表接收路径上丢包),而tx_queue和rx_queue则是内核层面为socket收发数据分配的buffer。

还可以通过/proc/net/snmp文件查看udp相关内容:

# cat /proc/net/snmp
Udp: InDatagrams NoPorts InErrors OutDatagrams RcvbufErrors SndbufErrors InCsumErrors IgnoredMulti MemErrors
Udp: 505 24 0 566 0 0 0 2695 0

重点关注InErrors和RcvbufErrors两个指标

当看到以上两个指标数值基本一样时,99.9%的问题出现在socket receive queue上了。

半连接队列和全连接队列溢出

相信大家对TCP三次握手四次挥手的流程已熟记心中(记不住也没关系,贴图了)

在建立TCP连接时,有两个非常重要的队列:半连接队列和全连接队列

  • 半连接队列:也称Syn队列,由/proc/sys/net/ipv4/tcp_max_syn_backlog指定,内核参数为net.ipv4.tcp_max_syn_backlog,存放收到SYN但未完成三次握手的连接,如果此队列满了,将不会保存新的tcp连接请求。
  • 全连接队列:也称Accept队列,由/proc/sys/net/core/somaxconn指定,内核参数为net.core.somaxconn,存放已完成三次握手、等待应用accept()的连接,如果此队列满了,新的连接请求到达时,将会被服务器拒绝或者丢弃,发送一个Connection refused错误信息到client。
网卡分片和组包时丢包

TSO和GSO:

TSO是利用网卡对TCP数据包分片,减轻CPU负荷的一种技术,也成为LSO(Large segment offload),其针对的是TCP,还有一种针对UDP的分片技术叫UFO。

GSO是TSO的增强,GSO不只针对TCP,针对任何协议,比TSO更通用,其会推迟数据分片直至发送到网卡驱动之前,此时会检查网卡是否支持分片功能(TSO、UFO),如果支持直接发送到网卡,如果不支持就在此时进行分片后再发往网卡。

如果网卡支持TSO/GSO,可以把最多64K大小的TCP payload(有效荷载)直接往下传给协议栈,此时ip层也不会进行分片,网卡会生成TCP/IP包头和帧头,这样可以offload很多协议栈上的内存操作,节省CPU资源。

查看网卡是否支持且开启TSO/GSO:

# ethtool -k enp125s0f0 | grep -E "tcp-segmentation-offload|generic-segmentation-offload"
tcp-segmentation-offload: on
generic-segmentation-offload: on

下图为关闭TSO/GSO、开启TSO和开启GSO的数据发送过程对比:

LRO和GRO:

网卡接收时同样有LROGRO

LRO是将网卡接收到的多个数据包合并成一个大的数据包,然后再传递给网络协议栈处理的技术,这样可以提高系统接收数据包的能力,减轻CPU负载。

GRO基本与LRO类似,客户了LRO的一些缺点,更通用,后续的驱动都使用GRO的接口。

查看网卡是否支持且开启LRO/GRO:

# ethtool -k enp125s0f0 | grep offload
generic-receive-offload: on
large-receive-offload: off [fixed]

数据接收过程基本与上图一直,只不过方向变为了由网卡发向应用程序,合包的步骤没有改变。

可能出现问题的场景:

当网卡仅支持TSO(发包)或LRO(收包)其一的时候,会导致数据包收发频率不一致,最终导致丢包的情况。现象大概率为仅收包时丢包,或是仅发包时丢包,这时需要注意检查其分包合包的配置。

开启或关闭分包和发包的命令如下:

# 示例
ethtool -K enp125s0f0 tso off
MTU/MSS导致丢包

MTU

Maximum Transmit Unit,最大传输单元,即物理接口(数据链路层)提供给其上层(通常为ip层)单次最大传输数据大小;以普遍使用的以太网接口为例,缺省值MTU=1500Byte,这是以太网接口对IP层的约束。

如果ip层有<=1500Byte需要发送,只需要一个ip包就可以完成发送任务;

如果ip层有>1500Byte数据需要发送,需要分片才能完成发送,这些分片有一个共同点,即Header ID相同。

查看MTU:

# ip a
2: enp125s0f0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000 # netstat -i
Kernel Interface table
Iface MTU RX-OK RX-ERR RX-DRP RX-OVR TX-OK TX-ERR TX-DRP TX-OVR Flg
enp125s0f0 1500 13429030 0 444287 0 19834143 0 0 0 BMRU # cat /sys/class/net/enp125s0f0/mtu
1500

修改MTU:

# 临时修改,重启后失效
ifconfig ens01 mtu 1200 # 永久修改
nmcli connection modify ens01 +ethernet.mtu 1200

MSS

Maximum Segment Size(最大报文段大小),TCP提交给ip层最大分段大小,不包含TCP Header和TCP Option,只包含TCP Payload。

MSS是TCP用来限制application层最大的发送字节数,如果底层物理接口MTU=1500Byte,则MSS=1500-20(IP Header)-20(TCP Header)=1460Byte,如果application有2000Byte的数据发送,需要两个segment才可以完成,一个1460,一个540。

UDP的MSS=1500-20(IP Header)=1480Byte,并且MSS值不能小于最小的advmss值ip_rt_min_advmss,可通过PROC文件设置此值,默认为256。

在TCP三次握手期间抓包,可以看到协商后设置的MSS值大小:

PMTU

PMTU是Path MTU的缩写,指的是在网络通信中,从源主机到目的主机所经过的所有网络设备中,能够通过的最大数据包大小。PMTU的原理是通过不断发送大小不同的数据包,直到发现一个最大的数据包能够成功传输,从而确定PMTU的大小。

其主要是应用在TCP/IP协议中,用于避免IP分片。当一个数据包的大小超过了某个网络设备的MTU时,该设备会将数据包分片,然后再将分片后的数据包发送出去。这样会增加网络传输的负担,降低网络传输的效率。因此,TCP/IP协议中的PMTU发现机制可以帮助避免IP分片,提高网络传输的效率。

系统中通过内核参数/proc/sys/net/ipv4/ip_no_pmtu_disc来控制全局默认策略,当为0时,表示进行PMTU的发现,为1时表示不尽兴PMTU的发现。

可以使用ping命令来检测当前PMTU:

# ping -c 3 -M do -s 1480 172.30.210.125
PING 172.30.210.125 (172.30.210.125) 1480(1508) bytes of data.
ping: local error: message too long, mtu=1500
ping: local error: message too long, mtu=1500
ping: local error: message too long, mtu=1500
3 packets transmitted, 0 received, +3 errors, 100% packet loss, time 2053ms

-s:指定发送ICMP包的大小

-M:传输过程中,是否允许分包。Do为不允许,当收到长度比自身设置的MTU大的IP数据包,则不能拆分,只能丢弃;want表示在本地分段;dont表示不涉及DF(Don't Fragment)标志。

ICMP的IP数据包 = IP首部 + ICMP首部 + ICMP的PDU

IP首部占用20字节,ICMP首部占用8字节,所以当-s 1480,以及-M do时,MTU值1480+20+8=1508,超过1500,导致发送失败,并返回了PMTU值大小为1500,当将-s指定为1472时,即可正常通信:

# ping -c 3 -M do -s 1472 172.30.210.125
PING 172.30.210.125 (172.30.210.125) 1472(1500) bytes of data.
1480 bytes from 172.30.210.125: icmp_seq=1 ttl=63 time=0.783 ms
1480 bytes from 172.30.210.125: icmp_seq=2 ttl=63 time=0.668 ms
1480 bytes from 172.30.210.125: icmp_seq=3 ttl=63 time=0.658 ms 3 packets transmitted, 3 received, 0% packet loss, time 2034ms

sock结构体中pmtudisc变量可通过setsockopt系统调用进行设置,用户也可以通过ip命令对MTU值进行锁定,不允许修改,如下命令将锁定到网关的mtu值为1300字节:

ip route add 0.0.0.0 via 192.168.18.252 mtu lock 1300

结语

本文主要对遇到过的网络问题总结一下排查思路,方便日后处理网络问题,仅为个人观点,为大家提供参考。

排查方法不一定全面,抛砖引玉,有更多网络知识、精通网络的大拿们可以将更好的思路分享出来,文中有描述错误的地方,也欢迎指正。

经验贴!万字总结网卡丢包及ping延迟等网络问题排查思路的更多相关文章

  1. netback的tasklet调度问题及网卡丢包的简单分析

    近期在万兆网卡上測试,出现了之前千兆网卡没有出现的一个现象,tasklet版本号的netback下,vm进行发包測试,发现vif的interrupt默认绑定在cpu0上,可是vm发包执行时发现host ...

  2. 修改网卡缓存,解决Linux 网卡丢包严重问题

    Linux 网卡丢包严重 生产中有一台linux设备并发比较大,droped包比较多,尤其是在跑游戏数据包的时候,存在严重的丢包现象,怀疑网卡性能不足,在更换设备前想能不有通过软件方法解决,通过网上一 ...

  3. ethtool 解决网卡丢包严重和网卡原理【转】

    转自:https://blog.csdn.net/u011857683/article/details/83758869 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog ...

  4. 用ethtool 命令解决Linux 网卡丢包【转】

    转自:https://blog.csdn.net/chengxuyuanyonghu/article/details/73739516 生产中有一台Linux设备并发比较大,droped包比较多,尤其 ...

  5. ethtool 解决网卡丢包严重和网卡原理

      1 概述 最近业务上老有问题,查看发现overruns值不断增加,学习了一下相关的知识.发现数值也在不停的增加.发现这些 errors, dropped, overruns 表示的含义还不大一样. ...

  6. Linux 网卡丢包严重

    http://hi.baidu.com/scstwy/item/cad0fbef1fdc18d3eb34c9d9

  7. 丢包 ICMP

    小结: 1.ICMP 常见网络丢包故障分析及处理 云极安 云极安 2019-12-25 我们在管理维护网络的过程中经常会遇到数据包丢失的现象.使用Ping命令进行连通性测试,则会发现Ping包延时远远 ...

  8. [转]网络性能评估工具Iperf详解(可测丢包率)

    原文链接:安全运维之:网络性能评估工具Iperf详解:http://os.51cto.com/art/201410/454889.htm 参考博文:http://linoxide.com/monito ...

  9. ping 丢包或不通时链路测试说明【转】

    转自:https://help.aliyun.com/knowledge_detail/40573.html?spm=5176.2020520165.121.d157.4fe170291Qdp4l#W ...

  10. linux 系统 UDP 丢包问题分析思路

    转自:http://cizixs.com/2018/01/13/linux-udp-packet-drop-debug?hmsr=toutiao.io&utm_medium=toutiao.i ...

随机推荐

  1. 浅聊java运行机制

    Java程序运行机制 首先要清楚运行机制一般有两种 解释型 编译型 解释型: 顾名思义,就像有个人在旁边给你解释东西一样.比如看一本英文书,英语老师在旁边一句一句给你翻译解释.在写源代码时,每写一个 ...

  2. TMS WEB Core的DEMO

    TMS WEB Core的思路就是把你界面设计转换成js.这个打通了,将会使生产效率呈几何级数提高. 说如何让其demo的能跑起来: 1.看图.增加参数(TMSHttpConfig.exe). 2.运 ...

  3. nodejs中使用websockets

    websockets介绍 websockets这个新协议为客户端提供了一个更快.更有效的通信线路.像HTTP一样,websockets运行在TCP连接之上,但是它们更快,因为我们不必每次都打开一个新的 ...

  4. Go操作MySQL总结

    1.下载驱动包 打开GoLand->Terminal,输入:go get github.com/go-sql-driver/mysql 2.编写代码 package mainimport ( & ...

  5. 🎀Java线程池创建

    简介 Java 手动创建线程池 代码 package com.zk.app.utils; import com.google.common.util.concurrent.ThreadFactoryB ...

  6. nohup启动jar包

    1. 后台启动jar包,并追加日志到日志文件run.log nohup java -jar wash-1.0-SNAPSHOT.jar >> run.log 2>&1 &am ...

  7. 如果 MySQL 中没有 MVCC,会有什么影响?

    如果 MySQL 中没有 MVCC,会有什么影响? MVCC(Multi-Version Concurrency Control) 是 MySQL(尤其是 InnoDB 存储引擎)中一个至关重要的并发 ...

  8. Excel导入操作,poi

    导入操作,仅供参考,具体情况具体而论 @Override public ReturnObject inforImport(LogySbjsJdsbqxxxParts entity, HttpServl ...

  9. 一个包含 80+ C#/.NET 编程技巧实战练习开源项目!

    项目介绍 C#/.NET/.NET Core编程常用语法.算法.技巧.中间件.类库.工作业务实操练习集,配套详细的文章教程讲解,助你快速掌握C#/.NET/.NET Core中各种编程常用语法.算法. ...

  10. 深入浅出了解生成模型-1:GAN模型原理以及代码实战

    更加好排版:https://www.big-yellow-j.top/posts/2025/05/08/GAN.html 日常使用比较多的生成模型比如GPT/Qwen等这些大多都是"文生文& ...