一、简介
工作中遇到的服务器,最常用的操作系统就是linux系统,linux 系统使用网络适配器和外部进行数据交换。当在高速链路或异常环境下进行网络通信时,就有可能出现网络数据丢包现象,接下来我主要要说的是:网路丢包的故障定位思路和解决方法。

二、相关原理介绍
1、网络消息的收发(报文收发过程)
在说丢包故障定位之前,我先来了介绍“网络报文收发过程”。本文以接收报文为例,发送报文与之类似,只是报文的传输方向相反。
1、网络packet首先通过网线被网卡获取,网卡检查packet的crc正常后,去掉packet头得到frame,如果frame中MAC的目的地址为本机地址,则接受该报文,否则丢弃(在混杂模式下也会接收该报文)
2、网卡将frame拷贝到网卡内部缓冲区中,一般是网卡的ring buffer中,拷贝完成后触发软中断通知内核处理
3、内核从ring buffer中拷贝网络数据,并传递给网络协议栈进行解析
4、协议栈解析完成后将数据放入ocket套接字的buffer中,最终传递给上层应用
2、相关名词解释
-
Bash 代码
enp125s0f0: flags=4163 mtu 1500inet 90.90.160.163 netmask 255.255.252.0 broadcast 90.90.163.255inet6 fe80::903a:4e71:69cd:eb09 prefixlen 64 scopeid 0x20ether 08:4f:0a:04:85:ac txqueuelen 1000 (Ethernet)RX packets 28356 bytes 4397271 (4.1 MiB)RX errors 0 dropped 15869 overruns 0 frame 0TX packets 3003 bytes 450378 (439.8 KiB)TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
RX errors:表示总的收包的错误数量
RX dropped:表示数据包已进入Ring buffer,但是由于系统原因(如内存不足)导致在拷贝到内存中的过程中被丢弃
RX overruns:表示数据还未进入网卡缓存(Ring buffer)时就被丢弃了,一般是由于Ring buffer中的数据未被及时取出导致溢出,新来的数据只能被丢弃。例如CPU负载大,导致处理网卡数据的速度小于网卡接收数据的速度,Ring buffer溢出。
RX frame:表示misaligend的frames数量
三、丢包故障定位与解决
1、网卡丢包
首先检查丢包是否是因为crc校验错误导致的:
Bash 代码
[root@localhost ~]# ethtool -S enp1s0f0 | grep crcrx_crc_errors_phy: 0
如果crc字段为非0,则表示网络报文在传输时出现了差错,此时可以更换线缆或网卡再做验证。
如果丢包发生在网卡上,则可以通过ethtool -S eth0 | egrep-i drop|error确认
Bash 代码
[root@localhost ~]# ethtool -S enp125s0f0 | egrep -i error|droprxq#0_rx_dropped: 0rxq#0_errors: 0rx_oq_drop_pkt_cnt: 0
netstat -i也可以提供网卡的收发报文和丢包情况,正常情况下error、drop和overrun字段应该为0
Bash 代码
如果RX_OVR一直在增加,说明Ringbuffer有溢出,除了Ring Buffer太小以外,有可能是CPU处于高负荷下,来不及从Ring buffer中获取数据,此时可以检查CPU高负荷的原因,对网卡进行中断亲和设置等操作。通过查看/proc/net/dev也可以查看是否有Ring buffer满而导致的丢包
[root@localhost ~]# netstat -i
Kernel Interface tableIface MTU RX-OK RX-ERR RX-DRP RX-OVR TX-OK TX-ERR TX-DRP TX-OVR Flgenp125s0f0 1500 29113 0 16149 0 3258 0 0 0 BMRUenp125s0f1 1500 0 0 0 0 0 0 0 0 BMUenp125s0f2 1500 0 0 0 0 0 0 0 0 BMUenp125s0f3 1500 0 0 0 0 0 0 0 0 BMRUenp1s0f0 1500 0 0 0 0 0 0 0 0 BMUenp1s0f1 1500 0 0 0 0 0 0 0 0 BMUlo 65536 1820 0 0 0 1820 0 0 0 LRU
如果硬件或者驱动没有问题,一般网卡丢包是由于Ring buffer太小导致,可以使用ethtool -G修改Ring buffer大小。
Bash 代码
[root@localhost ~]# cat proc/net/devInter-| Receive | Transmitface |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressedenp125s0f1: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0enp125s0f3: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0enp125s0f2: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0enp1s0f1: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0enp1s0f0: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0lo: 169176 1948 0 0 0 0 0 0 169176 1948 0 0 0 0 0 0enp125s0f0: 4880071 32037 0 18238 0 0 0 40408 486664 3302 0 0 0 0 0 0
2、内核丢包其中的fifo字段统计的是Ringbuffer满而丢弃的包
内核从网卡收到数据以后,交给协议栈处理之前会有缓冲队列backlog,每个CPU都有一个backlog队列,当从网卡中获取数据到backlog中的速率大于从backlog中将数据交给协议栈的速率时就会发生溢出。可以查看/proc/net/softnet_stat文件确认是否有backlog溢出:
-
Bash 代码
[root@localhost ~]# cat proc/net/softnet_stat00000003 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 0000000000000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 0000000000000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 0000000000000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 0000000000000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 0000000000000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 0000000000000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
每一行代表一个CPU core接收数据的情况
第1列表示收到的包总数
第2列是丢弃的包计数,此处的丢包指的是从网卡Ring buffer中输出到内核缓存队列时,由于队列满了而丢弃的数据包
第3列表示软中断一次取走netdev_budget个数据包,或取数据包时间超过2ms的次数
第4列~第8列固定为0,没有意义
第9列表示发送数据包时,对应的队列被锁住的次数
如果是因为backlog队列溢出导致的丢包,可以修改backlog队列的大小,通过systctl修改netdevmaxbacklog参数,默认大小为1000
-
Bash 代码
sysctl -w net.core.netdev_max_backlog=2000 |
在将数据交给内核协议栈后,协议栈进行分析处理。在协议栈中也有可能丢包,通过netstat-s可以查看是否有协议栈丢包,-u参数指定udp协议,-t参数指定tcp协议,也可以加-c参数持续输出,看各个数据的变化,本文以UDP协议为例进行讲解
-
Bash 代码
[root@localhost ~]# netstat -s -uIcmpMsg:InType3:1006OutType3:1006Udp:0 packetsreceived1006 packetsto unknown port received.0 packetreceive errors1081 packetssent0 receivebuffer errors0 sendbuffer errorsUdpLite:IpExt:InMcastPkts:3245OutMcastPkts: 11InBcastPkts:6213InOctets: 2859135OutOctets:633832InMcastOctets: 495247OutMcastOctets: 2658InBcastOctets: 2038580InNoECTPkts: 13895[root@localhost ~]#
packet receive errors表示接收有丢包
packets to unknown port received表示系统接收到的UDP报文的目标端口没有应用在监听,一般影响不严重
receive/send buffer errors表示收发队列太小导致的丢包数量
对于收发队列太小导致的丢包,可以通过调整收发队列参数来解决,系统默认的receive/sendbuffer大小如下:
-
Bash 代码
[root@localhost ~]# sysctlnet.core.rmem_defaultnet.core.rmem_default = 229376[root@localhost ~]# sysctlnet.core.wmem_defaultnet.core.wmem_default = 229376
可以使用以下命令修改buffer大小
-
Bash 代码
sysctl -w net.core.rmem_max=26214400 # 设置为 25Msysctl -w net.core.wmem_max=26214400 # 设置为 25M
通过查看/proc/net/snmp文件也可以查看各个协议的收发包情况:
-
Bash 代码
Ip: Forwarding DefaultTTL InReceives InHdrErrorsInAddrErrors ForwDatagrams InUnknownProtos InDiscards InDelivers OutRequestsOutDiscards OutNoRoutes ReasmTimeout ReasmReqds ReasmOKs ReasmFails FragOKsFragFails FragCreatesIp: 2 64 14757 0 28 0 0 0 4844 5745 512 0 0 0 0 0 0 00Icmp: InMsgs InErrors InCsumErrors InDestUnreachsInTimeExcds InParmProbs InSrcQuenchs InRedirects InEchos InEchoRepsInTimestamps InTimestampReps InAddrMasks InAddrMaskReps OutMsgs OutErrorsOutDestUnreachs OutTimeExcds OutParmProbs OutSrcQuenchs OutRedirects OutEchosOutEchoReps OutTimestamps OutTimestampReps OutAddrMasks OutAddrMaskRepsIcmp: 1038 0 0 1038 0 0 0 0 0 0 0 0 0 0 1038 0 1038 00 0 0 0 0 0 0 0 0IcmpMsg: InType3 OutType3IcmpMsg: 1038 1038Tcp: RtoAlgorithm RtoMin RtoMax MaxConn ActiveOpensPassiveOpens AttemptFails EstabResets CurrEstab InSegs OutSegs RetransSegsInErrs OutRsts InCsumErrorsTcp: 1 200 120000 -1 0 2 0 0 2 2768 3592 0 0 0 0Udp: InDatagrams NoPorts InErrors OutDatagramsRcvbufErrors SndbufErrors InCsumErrors IgnoredMultiUdp: 0 1038 0 1113 0 0 0 0UdpLite: InDatagrams NoPorts InErrors OutDatagramsRcvbufErrors SndbufErrors InCsumErrors IgnoredMultiUdpLite: 0 0 0 0 0 0 0 0/
3、应用丢包
内核协议栈把接收到的报文放到socket套接字的buffer中,应用程序从buffer中不断读取报文。所以这里有两个和应用程序有关的因素会影响丢包:socket buffer的大小和应用程序报文读取速度。
socket buffer大小可在应用程序初始化socket时设置,不过增大buffer的值会增加内存的使用,请根据实际情况配置;对于应用程序报文处理速度,应采用异步方式处理
四、其他定位方法
1、dropwatch
dropwatch可以输出数据包是在哪个内核函数中丢失的:
-
Bash 代码
[root@localhost ~]# dropwatch -l kasInitalizing kallsyms dbdropwatch> startEnabling monitoring...Kernel monitoring activated.Issue Ctrl-C to stop monitoring1 drops at skb_queue_purge+20 (0xffff000008708d3c)2 drops at __netif_receive_skb_core+504(0xffff00000871bd7c)1 drops at __netif_receive_skb_core+504(0xffff00000871bd7c)




