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 请求。

相关推荐
伤不起bb3 分钟前
Kafka 消息队列
linux·运维·分布式·kafka
Hello.Reader13 分钟前
Git 安装全攻略Linux、macOS、Windows 与源码编译
linux·git·macos
龙仔72516 分钟前
华为云CentOS配置在线yum源,连接公网后,逐步复制粘贴,看好自己对应的版本即可,【新手必看】
linux·centos·华为云
tiging17 分钟前
centos实现SSH远程登录
linux·centos·ssh
好多知识都想学1 小时前
Linux 文件处理器 sed 和 awk 详细讲解
linux·运维·ubuntu
Johny_Zhao2 小时前
阿里云数据库Inventory Hint技术分析
linux·mysql·信息安全·云计算·系统运维
FBI HackerHarry浩2 小时前
云计算 Linux Rocky day05【rpm、yum、history、date、du、zip、ln】
linux·运维·云计算·腾讯云
异常君3 小时前
Windows 与 Linux 虚拟内存机制对比:设计理念与实现差异
java·linux·windows
bcxwz6693 小时前
linux 故障处置通用流程-36计-14-27
linux·运维·服务器
孙克旭_3 小时前
day028-Shell自动化编程-判断进阶
linux·运维·数据库·自动化