记录一次服务器网络高延迟及严重丢包问题的排查

好久没更新了,水一篇(

最近发现服务器的网络变得非常奇怪,访问网页时经常会出现明显卡顿,有时候甚至需要等待好几秒才能打开。

本来以为只是网络抽风,结果一番折腾之后发现,问题居然是服务器 6Mbps 的公网带宽被一个下载请求吃满了。

既然前前后后查了不少东西,就简单记录一下排查过程。

问题表现

首先对服务器公网 IP 进行 Ping:

1
ping 43.143.47.70

结果出现了比较严重的丢包:

1
90 packets transmitted, 78 packets received, 13.3% packet loss

一开始怀疑是宽带的问题,于是切换到手机热点重新测试。

没想到更离谱:

1
94 packets transmitted, 71 packets received, 24.5% packet loss

换了两条完全不同的网络都存在严重丢包,问题似乎并不在本地。

随后使用 Curl 测试网站的 TCP 建连时间:

1
2
3
4
5
6
7
for i in {1..30}; do
curl -4 -o /dev/null -s \
--connect-timeout 5 \
-w 'TCP=%{time_connect} TTFB=%{time_starttransfer} TOTAL=%{time_total}\n' \
http://radar.astrophel.top
sleep 1
done

正常情况下 TCP 建连只需要约 40ms:

1
TCP=0.040793

但异常的时候会非常规律地变成:

1
2
3
TCP=1.038069
TCP=2.040431
TCP=3.037859

甚至偶尔直接连接超时。

这种 1 秒、2 秒、3 秒的延迟看起来非常像 TCP 重传。

抓包看看发生了什么

TCP 建立连接需要经过所谓的“三次握手”:

1
2
3
4
5
客户端                         服务器

SYN ------------------>
<------------------ SYN-ACK
ACK ------------------>

其中 SYN 可以简单理解成客户端说:

我要和你建立连接。

而 SYN-ACK 则是服务器回答:

收到,可以连接。

于是我在服务器上使用 tcpdump 抓取 TCP 握手:

1
2
sudo tcpdump -ni any -nn -tttt \
'host 客户端公网IP and tcp port 80 and (tcp[tcpflags] & tcp-syn != 0)'

很快就抓到了比较有意思的东西:

1
2
3
4
5
6
7
8
9
10
11
00:01:44.910  In   SYN
00:01:44.910 Out SYN-ACK

00:01:45.910 In SYN
00:01:45.910 Out SYN-ACK

00:01:46.912 In SYN
00:01:46.912 Out SYN-ACK

00:01:47.912 In SYN
00:01:47.912 Out SYN-ACK

服务器实际上每次收到 SYN 后,几十微秒内就发送了 SYN-ACK。

但是客户端并没有收到,所以每隔约 1 秒就重新发送一次 SYN。

刚好对应 Curl 中:

1
TCP=3.037859

也就是说,并不是 Nginx 或网页程序处理了三秒钟,而是服务器回复的数据包在回程过程中丢掉了。

意外发现

随后查看腾讯云监控,想看看是不是有什么恶意程序在占带宽,才发现了一个之前一直没有注意的问题。

服务器公网出带宽长期保持在:

1
5.3 ~ 5.8 Mbps

基本已经贴着 6Mbps 的上限运行。

于是使用 iftop 查看具体是谁在吃流量:

1
sudo iftop -nP -i eth0

结果发现一个 IP:

1
117.173.*.*

同时建立了大量 HTTPS 连接,而且服务器出流量已经达到了:

1
6 ~ 7 Mbps

进一步使用:

1
sudo ss -tnp | grep '117.173.*.*'

发现这些连接全部来自 Nginx。

最后查看 Nginx Access Log:

1
sudo grep '117.173.*.*' /var/log/nginx/access.log | tail -50

终于找到了罪魁祸首。

这个 IP 正在疯狂下载我的天气雷达历史数据:

1
2
3
GET /d/天气雷达归档/2025/04/20/xxxx.png
GET /d/天气雷达归档/2025/04/21/xxxx.png
...

每张 PNG 大约有 1MB,而且对方同时建立了大量下载连接。

于是:

1
2
3
4
5
6
7
8
9
10
11
大量并发下载

公网出带宽达到 6Mbps 上限

出口发生排队/丢包

SYN-ACK 也可能被丢掉

TCP 重传

网页偶尔需要等待 1~3 秒

这样之前所有奇怪的现象就都解释得通了。

更有意思的是,临时封禁这个 IP 后:

1
TX ≈ 几十 Kbps

解除封禁后:

1
TX ≈ 6 Mbps

瞬间又把带宽打满了(

Nginx 限制下载速度

当然,永久封 IP 并不是一个很好的解决办法。毕竟用户可能真的需要下载这套数据,而且换一个 IP 又可以继续下载。这说明自建下载站还是很需要做好限速设置的,不然流量跑干净了真的是欲哭无泪。

所以最后选择在 Nginx 中对网盘的 /d/ 下载地址增加限速。

首先在 http {} 中加入:

1
limit_conn_zone $binary_remote_addr zone=drive_perip:10m;

然后单独为 /d/ 设置规则:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
location ^~ /d/ {
# 同一个 IP 最多两个同时下载
limit_conn drive_perip 2;

# 单连接最高 150KB/s
limit_rate 150k;

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Range $http_range;
proxy_set_header If-Range $http_if_range;

proxy_redirect off;
proxy_pass http://127.0.0.1:5244;
proxy_http_version 1.1;

client_max_body_size 20000m;
}

这样同一个 IP 理论最大速度约为:

1
2
3
150 KB/s × 2
≈ 300 KB/s
≈ 2.4 Mbps

对于只有 6Mbps 公网带宽的小鸡来说已经比较合适了,至少不会再出现一个人下载文件把整个网站一起拖死的情况。

至此问题解决。

总结

这次一开始看到高延迟和 30% 左右的 Ping 丢包,还以为是什么复杂的运营商路由或者腾讯云网络故障。

结果绕了一圈发现原因其实非常朴素:公网带宽被下载流量吃满了。

不过也顺便第一次比较完整地用 pingcurltcpdumpssiftop 排查了一次 TCP 网络问题,也算是学到了不少奇怪的小知识。

以后遇到非常规律的延迟,大概第一时间就会想到 TCP 重传了。

以及最后的教训:

6Mbps 真的很小