网络丢包问题怎么定位解决?

 提示:点击图片可以放大
来源:优游ub8平台    发布时间:2024-07-03 17:00:41

  接下来,我就以最常用的反向代理服务器 Nginx 为例,带你一起看看如何分析网络丢包的问题。执行下面的 hping3 命令,进一步验证 Nginx 是否能正常访问。这里我没用 ping,是因为 ping 基于 ICMP 协议,而 Nginx 使用的是 TCP 协议。

  从 hping3 的输出中,我们大家可以发现,发送了 10 个请求包,却只收到了 5 个回复,50%的包都丢了。再观察每个请求的 RTT 不难发现,RTT 也有非常大的波动变化,小的时候只有 3ms,而大的时候则有 3s。

  根据这些输出,我们基本能判断,已发生了丢包现象。可以猜测,3s 的 RTT ,很可能是因为丢包后重传导致的。

  那到底是哪里发生了丢包呢?排查之前,我们大家可以回忆一下 Linux 的网络收发流程,先从理论上分析,哪里有一定的概率会发生丢包。你不妨拿出手边的笔和纸,边回忆边在纸上梳理,思考清楚再继续下面的内容。

  在这里,为了帮你理解网络丢包的原理,我画了一张图,你可以保存并打印出来使用。(图片放在文末了哈)

  从图中你能够准确的看出,有几率发生丢包的位置,实际上贯穿了整个网络协议栈。换句话说,全程都有丢包的可能。

  在两台 VM 连接之间,可能会发生传输失败的错误,比如网络拥塞、线路错误等;

  此外,如果配置了 iptables 规则,这些网络包也可能因为 iptables 过滤规则而丢包

  当然,上面这样一些问题,还有可能同时发生在通信的两台机器中。不过,由于我们没对 VM2做任何修改,并且 VM2 也只运行了一个最简单的 hping3 命令,这儿不妨假设它是没问题的。为了简化整个排查过程,我们还能更加进一步假设, VM1 的网络和内核配置也没问题。接下来,就可以从协议栈中,逐层排查丢包问题。

  当链路层由于缓冲区溢出等问题造成网卡丢包时,Linux 会在网卡收发数据的统计信息中记录下收发错误的次数。

  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 规则,并查看有没有丢包。

  能够正常的看到, eth0 上配置了一个网络模拟排队规则(qdisc netem),并且配置了丢包率为 30%(loss 30%)。

  看来应该就是这里导致 Nginx 回复的响应包被 netem 模块给丢了。

  不幸的是,从 hping3 的输出中可以看到还是 50% 的丢包,RTT 的波动也仍旧很大,从 3ms 到 1s。

  不过,既然链路层已经排查完了,我们就继续向上层分析,看看网络层和传输层有没有问题。

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

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

  不过,我们的目的是排查丢包问题,所以这里主要观察的是错误数、丢包数以及重传数。能够正常的看到,只有 TCP 协议发生了丢包和重传,分别是:

  这个结果告诉我们,TCP 协议有多次超时和失败重试,并且主要错误是半连接重置。

  换句话说,主要的失败,都是三次握手失败。不过,虽然在这儿看到了这么多失败,但具体失败的根源还是无法确定。

  首先,除了网络层和传输层的各种协议,iptables 和内核的连接跟踪机制也有一定可能会导致丢包。

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

  回顾一下 iptables 的原理,它基于 Netfilter 框架,通过一系列的规则,对网络数据包进行过滤(如防火墙)和修改(如 NAT)。

  这些 iptables 规则,统一管理在一系列的表中,包括 filter、nat、mangle(用于修改分组数据) 和 raw(用于原始数据包)等。

  而每张表又可以包括一系列的链,用于对 iptables 规则进行分组管理。

  要弄清楚这一点,就要求我们确认,那些目标为 DROP 和 REJECT 等会弃包的规则,有没有被执行到。

  可以直接查询 DROP 和 REJECT 等规则的统计信息,看看是否为0。

  从 iptables 的输出中,你能够正常的看到,两条 DROP 规则的统计数值不是 0,它们分别在INPUT 和 OUTPUT 链中。

  这两条规则其实就是一样的,指的是使用 statistic 模块,进行随机 30% 的丢包。

  0.0.0.0/0 表示匹配所有的源 IP 和目的 IP,也就是会对所有包都进行随机 30% 的丢包。

  不过,到目前为止,我们从始至终使用的 hping3 工具,只能验证案例 Nginx 的 80 端口处于正常监听状态,却还没有访问 Nginx 的 HTTP 服务。

  所以,不要匆忙下结论结束这次优化,我们还要进一步确认,Nginx 能不能正常响应 HTTP 请求。

  我们继续在终端二中,执行如下的 curl 命令,检查 Nginx 对 HTTP 请求的响应:

  奇怪,hping3 的结果为Nginx 的 80 端口是正常状态,为什么还是不能正常响应 HTTP 请求呢?

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

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

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

  根据 curl 设置的 3 秒超时选项,你应该能猜到,是因为 curl 命令超时后退出了。

  这里比较奇怪的是,我们并没有抓取到 curl 发来的 HTTP GET 请求。

  从 netstat 的输出中,你能够正常的看到,接收丢包数(RX-DRP)是 344,果然是在网卡接收时丢包了。

  不过问题也来了,为什么刚才用 hping3 时不丢包,现在换成 GET 就收不到了呢?

  其实,仔仔细细地观察上面 netstat 的输出界面,第二列正是每个网卡的 MTU 值。

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

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