Linux性能优化实战,网络丢包问题分析

在当今数字化时代,无论是搭建服务器、开发网络应用,还是进行云计算部署,Linux 系统都扮演着举足轻重的角色。作为一名运维人员或开发者,你肯定希望自己的 Linux 系统能够高效稳定地运行。但当网络丢包问题出现时,一切都变得糟糕起来,服务器响应迟缓,应用程序频繁报错,用户体验直线下降。

今天,我就带大家深入 Linux 性能优化的实战领域,一起揪出网络丢包这个 "罪魁祸首",从原理分析到排查方法,再到解决方案,全方位为你答疑解惑 ,让你的 Linux 系统重回高性能状态。

一、网络丢包概述

对于 Linux 系统的使用者来说,网络性能的优劣直接关系到系统的整体表现。而在网络性能问题中,网络丢包堪称最为棘手的难题之一,它就像隐藏在暗处的杀手,悄无声息地侵蚀着系统的性能。想象一下,当你在服务器上部署了一个关键的应用服务,满怀期待地等待用户的访问和使用。然而,用户却频繁反馈访问速度极慢,甚至出现连接中断的情况。经过一番排查,你发现罪魁祸首竟然是网络丢包。这时候,你就会深刻地意识到,网络丢包问题绝不是一个可以忽视的小麻烦。

从专业角度来看,网络丢包会带来一系列严重的后果。最直观的就是网络延迟的显著增加。当数据包在传输过程中被丢弃,接收方就无法及时收到完整的数据,这就需要发送方重新发送这些丢失的数据包。重传的过程无疑会消耗额外的时间,导致数据传输的延迟大幅上升。在一些对实时性要求极高的应用场景中,如在线游戏、视频会议等,哪怕是几毫秒的延迟增加都可能带来极差的用户体验。在在线游戏中,延迟的增加可能导致玩家的操作出现卡顿,无法及时响应游戏中的各种事件,严重影响游戏的流畅性和竞技性;在视频会议中,延迟则可能使画面出现卡顿、声音不同步等问题,让沟通变得异常困难。

网络丢包还会导致吞吐量的降低。吞吐量是指单位时间内成功传输的数据量,它是衡量网络性能的重要指标之一。当丢包发生时,一部分数据无法正常传输,这就必然会导致实际的吞吐量下降。对于一些大数据传输的场景,如文件下载、数据备份等,吞吐量的降低会大大延长传输时间,降低工作效率。如果你需要从远程服务器下载一个大型文件,原本预计几个小时就能完成的下载任务,可能因为网络丢包导致下载时间延长数倍,甚至可能因为丢包过于严重而导致下载失败,需要重新开始。

对于基于 TCP 协议的应用来说,丢包更是意味着网络拥塞和重传。TCP 协议具有可靠性机制,当它检测到数据包丢失时,会自动触发重传机制,以确保数据的完整性。然而,频繁的重传不仅会增加网络流量,还会进一步加剧网络拥塞,形成一种恶性循环。在高并发的网络环境中,这种恶性循环可能会导致整个网络的瘫痪,使所有依赖网络的应用都无法正常运行。

网络丢包对 Linux 系统性能的影响是多方面的,它不仅会降低用户体验,还会影响业务的正常运行,给企业带来巨大的损失。因此,解决网络丢包问题刻不容缓,这也是我们今天深入探讨 Linux 性能优化实战 ------ 网络丢包问题分析的重要原因。

在开始之前,我们先用一张图解释 linux 系统接收网络报文的过程:

  • 首先网络报文通过物理网线发送到网卡

  • 网络驱动程序会把网络中的报文读出来放到 ring buffer 中,这个过程使用 DMA(Direct Memory Access),不需要 CPU 参与

  • 内核从 ring buffer 中读取报文进行处理,执行 IP 和 TCP/UDP 层的逻辑,最后把报文放到应用程序的 socket buffer 中

  • 应用程序从 socket buffer 中读取报文进行处理

二、探寻"凶手":丢包可能发生在哪

当网络丢包问题出现时,就如同一场悬疑案件,我们需要抽丝剥茧,从各个层面去探寻 "凶手",也就是丢包发生的原因。在 Linux 系统中,丢包可能发生在网络协议栈的各个层次,每个层次都有其独特的丢包原因和排查方法。

2.1收包流程:数据包的 "入境之路"

当网卡接收到报文时,这场 "入境之旅" 就开启了。首先,网卡通过 DMA(直接内存访问)技术,以极高的效率将数据包拷贝到 RingBuf(环形缓冲区)中,就好比货物被快速卸到了一个临时仓库。紧接着,网卡向 CPU 发起一个硬中断,就像吹响了紧急集合哨,通知 CPU 有数据抵达 "国门"。

CPU 迅速响应,开始执行对应的硬中断处理例程,在这个例程里,它会将数据包的相关信息放入每 CPU 变量 poll_list 中,随后触发一个收包软中断,把后续的精细活儿交给软中断去处理。对应 CPU 的软中断线程 ksoftirqd 就登场了,它负责处理网络包接收软中断,具体来说,就是执行 net_rx_action () 函数。

在这个函数的 "指挥" 下,数据包从 RingBuf 中被小心翼翼地取出,然后进入协议栈,开启层层闯关。从链路层开始,检查报文合法性,剥去帧头、帧尾,接着进入网络层,判断包的走向,若是发往本机,再传递到传输层。最终,数据包被妥妥地放到 socket 的接收队列中,等待应用层随时来收取,至此,数据包算是顺利 "入境",完成了它的收包流程。

2.2发包流程:数据包的 "出境之旅"

应用程序要发送数据时,数据包的 "出境之旅" 便启程了。首先,应用程序调用 Socket API(比如 sendmsg)发送网络包,这一操作触发系统调用,使得数据从用户空间拷贝到内核空间,同时,内核会为其分配一个 skb(sk_buff 结构体,它可是数据包在内核中的 "代言人",承载着各种关键信息),并将数据封装其中。接着,skb 进入协议栈,开始自上而下的 "闯关升级"。

在传输层,会为数据添加 TCP 头或 UDP 头,进行拥塞控制、滑动窗口等一系列精细操作;到了网络层,依据目标 IP 地址查找路由表,确定下一跳,填充 IP 头中的源和目标 IP 地址、TTL 等关键信息,还可能进行 skb 切分,同时要经过 netfilter 框架的 "安检",判断是否符合过滤规则。

之后,在邻居子系统填充目的 MAC 地址,再进入网络设备子系统,skb 被放入发送队列 RingBuf 中,等待网卡发送。网卡发送完成后,会向 CPU 发出一个硬中断,告知 "任务完成",这个硬中断又会触发软中断,在软中断处理函数中,对 RingBuf 进行清理,把已经发送成功的数据包残留信息清除掉,就像清理运输后的车厢,为下一次运输做好准备,至此,数据包顺利 "出境",完成了它的发包流程。

三、链路层详解

当链路层由于缓冲区溢出等原因导致网卡丢包时,Linux 会在网卡收发数据的统计信息中记录下收发错误的次数。链路层是网络通信的基础,它负责将网络层传来的数据封装成帧,并通过物理介质进行传输。链路层丢包通常是由于硬件故障、网络拥塞或者配置错误等原因导致的。当链路层由于缓冲区溢出等原因导致网卡丢包时,Linux 会在网卡收发数据的统计信息中记录下收发错误的次数。

我们可以通过 ethtool 或者 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
eth0       100       31      0      0 0             8      0      0      0 BMRU
lo       65536        0      0      0 0             0      0      0      0 LRU

RX-OK、RX-ERR、RX-DRP、RX-OVR ,分别表示接收时的总包数、总错误数、进入 Ring Buffer 后因其他原因(如内存不足)导致的丢包数以及 Ring Buffer 溢出导致的丢包数。

TX-OK、TX-ERR、TX-DRP、TX-OVR 也代表类似的含义,只不过是指发送时对应的各个指标。

这里我们没有发现任何错误,说明虚拟网卡没有丢包。不过要注意,如果用 tc 等工具配置了 QoS,那么 tc 规则导致的丢包,就不会包含在网卡的统计信息中。所以接下来,我们还要检查一下 eth0 上是否配置了 tc 规则,并查看有没有丢包。添加 -s 选项,以输出统计信息:

tc -s qdisc show dev eth0
 
qdisc netem 800d: root refcnt 2 limit 1000 loss 30%
 Sent 432 bytes 8 pkt (dropped 4, overlimits 0 requeues 0)
 backlog 0b 0p requeues 0

可以看到, eth0 上配置了一个网络模拟排队规则(qdisc netem),并且配置了丢包率为 30%(loss 30%)。再看后面的统计信息,发送了 8 个包,但是丢了 4个。看来应该就是这里导致 Nginx 回复的响应包被 netem 模块给丢了。

既然发现了问题,解决方法也很简单,直接删掉 netem 模块就可以了。执行下面的命令,删除 tc 中的 netem 模块:

tc qdisc del dev eth0 root netem loss 30%

删除后,重新执行之前的 hping3 命令,看看现在还有没有问题:

hping3 -c 10 -S -p 80 192.168.0.30
 
HPING 192.168.0.30 (eth0 192.168.0.30): S set, 40 headers + 0 data bytes
len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=0 win=5120 rtt=7.9 ms
len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=2 win=5120 rtt=1003.8 ms
len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=5 win=5120 rtt=7.6 ms
len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=6 win=5120 rtt=7.4 ms
len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=9 win=5120 rtt=3.0 ms
 
--- 192.168.0.30 hping statistic ---
10 packets transmitted, 5 packets received, 50% packet loss
round-trip min/avg/max = 3.0/205.9/1003.8 ms

不幸的是,从 hping3 的输出中可以看到还是 50% 的丢包,RTT 的波动也仍旧很大,从 3ms 到 1s。显然,问题还是没解决,丢包还在继续发生。不过,既然链路层已经排查完了,我们就继续向上层分析,看看网络层和传输层有没有问题。

四、网络层和传输层

在网络层和传输层中,引发丢包的因素非常多。不过,其实想确认是否丢包,是非常简单的事,因为 Linux 已经为我们提供了各个协议的收发汇总情况。

⑴网络层

网络层负责将数据包从源地址传输到目的地址,它主要处理路由选择、IP 地址解析等功能。网络层丢包可能是由于路由失败、组包大小超过 MTU(最大传输单元)等原因引起的。当数据包的大小超过了网络中某条链路的 MTU 时,数据包就需要被分片传输,如果分片过程出现问题,或者在重组过程中丢失了部分分片,就会导致丢包。我们可以通过netstat -s命令查看 IP 层的统计信息,其中IpInReceives表示接收到的 IP 数据包总数,IpInDelivers表示成功交付给上层协议的 IP 数据包数,如果两者之间的差值较大,就可能存在网络层丢包的情况。还可以查看IpOutNoRoutes指标,它表示因为找不到路由而丢弃的数据包数,如果这个值不断增加,说明可能存在路由问题导致丢包。

⑵传输层

传输层负责为应用层提供端到端的通信服务,常见的传输层协议有 TCP 和 UDP。传输层丢包可能是由于端口未监听、资源占用超过内核限制等原因造成的。在高并发的网络环境中,如果应用程序创建了大量的 TCP 连接,而系统资源(如文件描述符、内存等)有限,就可能导致部分连接无法正常建立或维持,从而出现丢包现象。我们可以通过netstat -s命令查看 TCP 和 UDP 协议的统计信息,比如TcpRetransSegs表示 TCP 重传的数据包数,如果这个值较大,说明可能存在传输层丢包导致的重传。UdpInErrors表示接收到的 UDP 错误数据包数,如果该值不为零,也提示可能存在 UDP 丢包问题。

执行 netstat -s 命令,可以看到协议的收发汇总,以及错误信息:

netstat -s
#输出
Ip:
    Forwarding: 1          //开启转发
    31 total packets received    //总收包数
    0 forwarded            //转发包数
    0 incoming packets discarded  //接收丢包数
    25 incoming packets delivered  //接收的数据包数
    15 requests sent out      //发出的数据包数
Icmp:
    0 ICMP messages received    //收到的ICMP包数
    0 input ICMP message failed    //收到ICMP失败数
    ICMP input histogram:
    0 ICMP messages sent      //ICMP发送数
    0 ICMP messages failed      //ICMP失败数
    ICMP output histogram:
Tcp:
    0 active connection openings  //主动连接数
    0 passive connection openings  //被动连接数
    11 failed connection attempts  //失败连接尝试数
    0 connection resets received  //接收的连接重置数
    0 connections established    //建立连接数
    25 segments received      //已接收报文数
    21 segments sent out      //已发送报文数
    4 segments retransmitted    //重传报文数
    0 bad segments received      //错误报文数
    0 resets sent          //发出的连接重置数
Udp:
    0 packets received
    ...
TcpExt:
    11 resets received for embryonic SYN_RECV sockets  //半连接重置数
    0 packet headers predicted
    TCPTimeouts: 7    //超时数
    TCPSynRetrans: 4  //SYN重传数
  ...

etstat 汇总了 IP、ICMP、TCP、UDP 等各种协议的收发统计信息。不过,我们的目的是排查丢包问题,所以这里主要观察的是错误数、丢包数以及重传数。可以看到,只有 TCP 协议发生了丢包和重传,分别是:

  • 11 次连接失败重试(11 failed connection attempts)

  • 4 次重传(4 segments retransmitted)

  • 11 次半连接重置(11 resets received for embryonic SYN_RECV sockets)

  • 4 次 SYN 重传(TCPSynRetrans)

  • 7 次超时(TCPTimeouts)

这个结果告诉我们,TCP 协议有多次超时和失败重试,并且主要错误是半连接重置。换句话说,主要的失败,都是三次握手失败。不过,虽然在这儿看到了这么多失败,但具体失败的根源还是无法确定。

五、iptables规则

iptables 是 Linux 系统中常用的防火墙工具,它可以根据用户定义的规则对网络数据包进行过滤和处理。如果 iptables 的规则配置不当,就可能导致数据包被错误地丢弃。我们可以通过iptables -L -n命令查看当前的 iptables 规则,检查是否存在不合理的规则,如错误的端口过滤、源地址或目的地址限制等。也可以使用iptables -v -L命令查看规则的统计信息,了解哪些规则被频繁匹配,从而判断是否是 iptables 规则导致了丢包。

首先,除了网络层和传输层的各种协议,iptables 和内核的连接跟踪机制也可能会导致丢包。所以,这也是发生丢包问题时我们必须要排查的一个因素。

先来看看连接跟踪,要确认是不是连接跟踪导致的问题,只需要对比当前的连接跟踪数和最大连接跟踪数即可。

# 主机终端中查询内核配置
$ sysctl net.netfilter.nf_conntrack_max
net.netfilter.nf_conntrack_max = 262144
$ sysctl net.netfilter.nf_conntrack_count
net.netfilter.nf_conntrack_count = 182

可以看到,连接跟踪数只有 182,而最大连接跟踪数则是 262144。显然,这里的丢包,不可能是连接跟踪导致的。

接着,再来看 iptables。回顾一下 iptables 的原理,它基于 Netfilter 框架,通过一系列的规则,对网络数据包进行过滤(如防火墙)和修改(如 NAT)。这些 iptables 规则,统一管理在一系列的表中,包括 filter、nat、mangle(用于修改分组数据) 和 raw(用于原始数据包)等。而每张表又可以包括一系列的链,用于对 iptables 规则进行分组管理。

对于丢包问题来说,最大的可能就是被 filter 表中的规则给丢弃了。要弄清楚这一点,就需要我们确认,那些目标为 DROP 和 REJECT 等会弃包的规则,有没有被执行到。可以直接查询 DROP 和 REJECT 等规则的统计信息,看看是否为0。如果不是 0 ,再把相关的规则拎出来进行分析。

iptables -t filter -nvL
#输出
Chain INPUT (policy ACCEPT 25 packets, 1000 bytes)
 pkts bytes target     prot opt in     out     source               destination
    6   240 DROP       all  --  *      *       0.0.0.0/0            0.0.0.0/0            statistic mode random probability 0.29999999981
 
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination
 
Chain OUTPUT (policy ACCEPT 15 packets, 660 bytes)
 pkts bytes target     prot opt in     out     source               destination
    6   264 DROP       all  --  *      *       0.0.0.0/0            0.0.0.0/0            statistic mode random probability 0.29999999981

从 iptables 的输出中,你可以看到,两条 DROP 规则的统计数值不是 0,它们分别在INPUT 和 OUTPUT 链中。这两条规则实际上是一样的,指的是使用 statistic 模块,进行随机 30% 的丢包。0.0.0.0/0 表示匹配所有的源 IP 和目的 IP,也就是会对所有包都进行随机 30% 的丢包。看起来,这应该就是导致部分丢包的"罪魁祸首"了。

执行下面的两条 iptables 命令,删除这两条 DROP 规则。

root@nginx:/# iptables -t filter -D INPUT -m statistic --mode random --probability 0.30 -j DROP
root@nginx:/# iptables -t filter -D OUTPUT -m statistic --mode random --probability 0.30 -j DROP

再次执行刚才的 hping3 命令,看看现在是否正常

hping3 -c 10 -S -p 80 192.168.0.30
#输出
HPING 192.168.0.30 (eth0 192.168.0.30): S set, 40 headers + 0 data bytes
len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=0 win=5120 rtt=11.9 ms
len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=1 win=5120 rtt=7.8 ms
...
len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=9 win=5120 rtt=15.0 ms
 
--- 192.168.0.30 hping statistic ---
10 packets transmitted, 10 packets received, 0% packet loss
round-trip min/avg/max = 3.3/7.9/15.0 ms

这次输出你可以看到,现在已经没有丢包了,并且延迟的波动变化也很小。看来,丢包问题应该已经解决了。

不过,到目前为止,我们一直使用的 hping3 工具,只能验证案例 Nginx 的 80 端口处于正常监听状态,却还没有访问 Nginx 的 HTTP 服务。所以,不要匆忙下结论结束这次优化,我们还需要进一步确认,Nginx 能不能正常响应 HTTP 请求。我们继续在终端二中,执行如下的 curl 命令,检查 Nginx 对 HTTP 请求的响应:

$ curl --max-time 3 http://192.168.0.30
curl: (28) Operation timed out after 3000 milliseconds with 0 bytes received

奇怪,hping3 的结果显示Nginx 的 80 端口是正常状态,为什么还是不能正常响应 HTTP 请求呢?别忘了,我们还有个大杀器------抓包操作。看来有必要抓包看看了。

六、tcpdump命令工具使用

执行下面的 tcpdump 命令,抓取 80 端口的包如下:

tcpdump -i eth0 -nn port 80
#输出
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes

然后,切换到终端二中,再次执行前面的 curl 命令:

curl --max-time 3 http://192.168.0.30
curl: (28) Operation timed out after 3000 milliseconds with 0 bytes received

等到 curl 命令结束后,再次切换回终端一,查看 tcpdump 的输出:

14:40:00.589235 IP 10.255.255.5.39058 > 172.17.0.2.80: Flags [S], seq 332257715, win 29200, options [mss 1418,sackOK,TS val 486800541 ecr 0,nop,wscale 7], length 0
14:40:00.589277 IP 172.17.0.2.80 > 10.255.255.5.39058: Flags [S.], seq 1630206251, ack 332257716, win 4880, options [mss 256,sackOK,TS val 2509376001 ecr 486800541,nop,wscale 7], length 0
14:40:00.589894 IP 10.255.255.5.39058 > 172.17.0.2.80: Flags [.], ack 1, win 229, options [nop,nop,TS val 486800541 ecr 2509376001], length 0
14:40:03.589352 IP 10.255.255.5.39058 > 172.17.0.2.80: Flags [F.], seq 76, ack 1, win 229, options [nop,nop,TS val 486803541 ecr 2509376001], length 0
14:40:03.589417 IP 172.17.0.2.80 > 10.255.255.5.39058: Flags [.], ack 1, win 40, options [nop,nop,TS val 2509379001 ecr 486800541,nop,nop,sack 1 {76:77}], length 0

从 tcpdump 的输出中,我们就可以看到:

  • 前三个包是正常的 TCP 三次握手,这没问题;

  • 但第四个包却是在 3 秒以后了,并且还是客户端(VM2)发送过来的 FIN 包,说明客户端的连接关闭了

根据 curl 设置的 3 秒超时选项,你应该能猜到,这是因为 curl 命令超时后退出了。用 Wireshark 的 Flow Graph 来表示,你可以更清楚地看到上面这个问题:

这里比较奇怪的是,我们并没有抓取到 curl 发来的 HTTP GET 请求。那究竟是网卡丢包了,还是客户端就没发过来呢?

可以重新执行 netstat -i 命令,确认一下网卡有没有丢包问题:

netstat -i
 
Kernel Interface table
Iface      MTU    RX-OK RX-ERR RX-DRP RX-OVR    TX-OK TX-ERR TX-DRP TX-OVR Flg
eth0       100      157      0    344 0            94      0      0      0 BMRU
lo       65536        0      0      0 0             0      0      0      0 LRU

从 netstat 的输出中,你可以看到,接收丢包数(RX-DRP)是 344,果然是在网卡接收时丢包了。不过问题也来了,为什么刚才用 hping3 时不丢包,现在换成 GET 就收不到了呢?还是那句话,遇到搞不懂的现象,不妨先去查查工具和方法的原理。我们可以对比一下这两个工具:

  • hping3 实际上只发送了 SYN 包;

  • curl 在发送 SYN 包后,还会发送 HTTP GET 请求。HTTP GET本质上也是一个 TCP 包,但跟 SYN 包相比,它还携带了 HTTP GET 的数据。

通过这个对比,你应该想到了,这可能是 MTU 配置错误导致的。为什么呢?

其实,仔细观察上面 netstat 的输出界面,第二列正是每个网卡的 MTU 值。eth0 的 MTU只有 100,而以太网的 MTU 默认值是 1500,这个 100 就显得太小了。当然,MTU 问题是很好解决的,把它改成 1500 就可以了。

ifconfig eth0 mtu 1500

修改完成后,再切换到终端二中,再次执行 curl 命令,确认问题是否真的解决了:

curl --max-time 3 http://192.168.0.30/
#输出
<!DOCTYPE html>
<html>
...
<p><em>Thank you for using nginx.</em></p>
</body>
</html>

非常不容易呀,这次终于看到了熟悉的 Nginx 响应,说明丢包的问题终于彻底解决了。

七、实战演练:排查与解决 Nginx 丢包问题

理论上的分析固然重要,但实际操作才是检验真理的关键。下面,我们将通过一个具体的案例,以 Nginx 应用为例,深入探讨如何在实际场景中排查和解决网络丢包问题。

7.1模拟访问与初步判断

假设我们在一台 Linux 服务器上部署了 Nginx 应用,现在怀疑它存在网络丢包问题。我们首先使用 hping3 命令来模拟访问 Nginx 服务。hping3 是一个功能强大的网络工具,它可以发送各种类型的网络数据包,帮助我们测试网络的连通性和性能。执行以下命令:

hping3 -c 10 -S -p 80 192.168.0.30

在这个命令中,-c 10表示发送 10 个请求包,-S表示使用 TCP SYN 标志位,-p 80指定目标端口为 80,即 Nginx 服务默认的端口,192.168.0.30是 Nginx 服务器的 IP 地址。执行命令后,我们得到如下输出:

HPING 192.168.0.30 (eth0 192.168.0.30): S set, 40 headers + 0 data bytes
len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=3 win=5120 rtt=7.5 ms
len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=4 win=5120 rtt=7.4 ms
len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=5 win=5120 rtt=3.3 ms
len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=7 win=5120 rtt=3.0 ms
len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=6 win=5120 rtt=3027.2 ms
--- 192.168.0.30 hping statistic ---
10 packets transmitted, 5 packets received, 50% packet loss
round-trip min/avg/max = 3.0/609.7/3027.2 ms

从输出结果中,我们可以清晰地看到,总共发送了 10 个请求包,但只收到了 5 个回复包,丢包率高达 50%。而且,每个请求的 RTT(往返时间)波动非常大,最小值只有 3.0ms,而最大值却达到了 3027.2ms,这表明网络中很可能存在丢包现象。

7.2链路层排查

初步判断存在丢包问题后,我们首先从链路层开始排查。使用netstat -i命令查看虚拟网卡的丢包记录:

netstat -i

得到如下输出:

Kernel Interface table
Iface      MTU    RX-OK RX-ERR RX-DRP RX-OVR    TX-OK TX-ERR TX-DRP TX-OVR Flg
eth0      100       31      0      0    0             8      0      0      0 BMRU
lo       65536        0      0      0    0             0      0      0      0 LRUR

在这个输出中,RX-OK表示接收时的总包数,RX-ERR表示总错误数,RX-DRP表示进入 Ring Buffer 后因其他原因(如内存不足)导致的丢包数,RX-OVR表示 Ring Buffer 溢出导致的丢包数,TX-OK至TX-OVR则表示发送时的相应指标。从这里可以看出,虚拟网卡的各项错误指标均为 0,说明虚拟网卡本身没有丢包。

不过,如果使用tc等工具配置了 QoS(Quality of Service,服务质量),tc规则导致的丢包不会包含在网卡的统计信息中。因此,我们还需要检查eth0上是否配置了tc规则,并查看是否有丢包。添加-s选项以输出统计信息:

tc -s qdisc show dev eth0

输出结果如下:

qdisc netem 800d: root refcnt 2 limit 1000 loss 30%
Sent 432 bytes 8 pkt (dropped 4, overlimits 0 requeues 0)
backlog 0b 0p requeues 0

可以看到,eth0上配置了一个网络模拟排队规则qdisc netem,并且设置了丢包率为 30%(loss 30%)。从后面的统计信息可知,发送了 8 个包,但丢了 4 个。这很可能就是导致 Nginx 回复的响应包被netem模块丢弃的原因。既然找到了问题,解决方法就很简单,直接删除netem模块:

tc qdisc del dev eth0 root netem loss 30%

删除后,重新执行hping3命令,看看问题是否解决。然而,从hping3的输出中发现,仍然有 50% 的丢包,RTT 波动依旧很大,说明问题还未得到解决,需要继续向上层排查。

网络层和传输层排查

接下来,我们排查网络层和传输层。在这两层中,引发丢包的因素众多,但确认是否丢包却相对简单,因为 Linux 已经为我们提供了各个协议的收发汇总情况。执行netstat -s命令,查看 IP、ICMP、TCP 和 UDP 等协议的收发统计信息:

netstat -s

输出结果非常丰富,这里我们重点关注与丢包相关的信息。例如,从 TCP 协议的统计信息中,我们看到有多次超时和失败重试,并且主要错误是半连接重置,这表明可能存在三次握手失败的问题。这可能是由于网络拥塞、端口被占用、防火墙限制等原因导致的。此时,我们需要进一步分析具体的错误原因,可以结合其他工具和命令,如lsof查看端口占用情况,检查防火墙规则等。

7.3iptables 排查

iptables 是 Linux 系统中常用的防火墙工具,其规则配置不当可能导致数据包被丢弃。首先,我们检查内核的连接跟踪机制,查看当前的连接跟踪数和最大连接跟踪数:

cat /proc/sys/net/nf_conntrack_count
cat /proc/sys/net/nf_conntrack_max

假设连接跟踪数只有 182,而最大连接跟踪数是 262144,说明连接跟踪数没有达到上限,不存在因连接跟踪数满而导致丢包的问题。

接着,查看 iptables 规则,使用iptables -L -n命令:

iptables -L -n

在输出的规则列表中,我们发现有两条DROP规则,使用了statistic模块进行随机 30% 的丢包。这显然是导致丢包的一个重要原因。我们将这两条规则直接删除,然后重新执行hping3命令。此时,hping3的输出显示已经没有丢包,这说明 iptables 的错误规则是导致之前丢包的原因之一。

端口状态检查与进一步排查

虽然hping3验证了 Nginx 的 80 端口处于正常监听状态,但还需要检查 Nginx 对 HTTP 请求的响应。使用curl命令:

curl -w 'Http code: %{http_code}\\nTotal time:%{time_total}s\\n' -o /dev/null --connect-timeout 10 http://192.168.0.30/

结果发现连接超时,这表明虽然端口监听正常,但 Nginx 在处理 HTTP 请求时可能存在问题。为了进一步分析,我们使用tcpdump命令抓包:

tcpdump -i eth0 -n tcp port 80

在另一个终端执行curl命令,然后查看tcpdump的输出。发现前三个包是正常的 TCP 三次握手,但第四个包却是在 3 秒后才收到,并且是客户端发送过来的 FIN 包,这说明客户端的连接已经关闭。

重新执行netstat -i命令,检查网卡是否有丢包,发现果然是在网卡接收时丢包了。进一步检查最大传输单元 MTU(Maximum Transmission Unit):

ifconfig eth0 | grep MTU

发现eth0的 MTU 只有 100,而以太网的 MTU 默认值是 1500。MTU 过小可能导致数据包在传输过程中需要分片,从而增加丢包的风险。我们将 MTU 修改为 1500:

ifconfig eth0 mtu 1500

再次执行curl命令,问题得到解决,Nginx 能够正常响应 HTTP 请求。

相关推荐
银河麒麟操作系统12 分钟前
【银河麒麟高级服务器操作系统】系统日志Call trace现象分析及处理全流程
linux·运维·服务器·网络
烛.照10318 分钟前
rabbitMQ消息转换器
java·linux·rabbitmq
入眼皆含月36 分钟前
《Linux基础优化与常用软件包》
linux·运维·服务器
_清风来叙1 小时前
【Ubuntu】ARM交叉编译开发环境解决“没有那个文件或目录”问题
linux·arm开发·ubuntu
学Linux的语莫2 小时前
k8s中,一.pod污点,二.pod容器污点容忍策略,三.pod优先级(PriorityClass类)
linux·docker·容器·kubernetes
m0_748249542 小时前
Linux环境下Tomcat的安装与配置详细指南
linux·运维·tomcat
随缘与奇迹2 小时前
linux中,软硬链接的作用和使用
linux·运维·服务器
m0_748255652 小时前
Linux环境下的事件驱动力量:探索Libevent的高性能IO架构
linux·架构·php
努力成为DBA的小王2 小时前
Oracle(windows安装遇到的ORA-12545、ORA-12154、ORA-12541、ORA-12514等问题)
linux·运维·服务器·数据库·oracle
m0_748230212 小时前
Text2Sql:开启自然语言与数据库交互新时代(3030)
数据库·oracle·性能优化