⚠️ AI 生成标识 · 本文由 AI Agent 协助撰写,人类作者审核发布。
起因
我把服务器从阿里云迁移到了京东云,顺便把 AI 助手从 v1.x 升级到了 v2.0.0。结果发现助手回复消息变得极其缓慢——有时候一条消息要等 5 分钟才收到回复。
一开始我以为是大版本升级导致的 Bug,或者模型推理变慢了。但排查后发现,问题跟代码无关,跟模型无关,纯粹是网络问题。
排查过程
第一轮:直觉陷阱
我第一个反应是”模型推理变慢了”——每次助手回复前都要调大模型,多轮 tool call 串行执行,每次推理都要重新等模型返回,加起来自然就慢了。
但这个推理被一个关键信息推翻了:之前从来没这么慢过,换服务器 + 升版本后才出现的。 如果仅仅是模型推理慢,那应该在旧服务器上也一样慢才对。
第二轮:看日志
我让助手去查运行日志,发现了大量这样的错误:
1
2
ConnectionTimeoutError: Connection timeout to host
https://api.sgroup.qq.com/v2/users/.../messages
这不是模型推理慢,而是消息发不出去! 助手早就推理完了,但往 QQ 的 API 发消息时连接超时了。
第三轮:拆解网络
为了搞清楚到底是哪一步出了问题,我设计了几个测试来隔离变量:
| 测试 | 目的 | 方法 |
|---|---|---|
| 测试1 | 检查 TCP 连接 | 纯 socket 连接,不涉及 TLS |
| 测试2 | 检查 TLS 握手 | socket + ssl 完整握手 |
| 测试3 | 走代理对比 | 通过代理服务器访问 |
| 测试4 | 排除软件干扰 | 用 curl 空环境变量直连 |
第四轮:实锤
结果非常清晰:
1
2
3
腾讯QQ API: TCP=0.007s ✅ → TLS=❌ 8.0s 超时
腾讯官网: TCP=0.025s ✅ → TLS=0.043s ✅
百度: TCP=0.010s ✅ → TLS=0.040s ✅
TCP 三次握手 7 毫秒就通了,但在 TLS 握手阶段卡死了 8 秒。
更离谱的是,这个问题是间歇性发作的——同一台服务器,同一套代码,同一个 API,有时候 50 次测试全部成功(平均 0.08s),有时候 20 次测试失败 18 次(成功率 10%)。
转折:真相比想象的更简单
到这里我一度认为是跨云网络的中间设备(防火墙/DPI)对 TLS ClientHello 包做深度检测导致的随机丢包。直到我随手跑了个 ping——结果让我大跌眼镜。
ping 命令揭开真相
1
2
3
$ ping api.sgroup.qq.com
PING ins-npwk3wle.ias.tencent-cloud.net (175.27.13.145)
→ 100 packets transmitted, 25 received, 75% packet loss 🔴
75% 的 ICMP 丢包率? 这比 TLS 握手还夸张。ping 是纯 ICMP 包,连端口都不需要——如果 ping 都丢 75%,那别的协议怎么可能正常?
但等等,我再跑一次试试:
1
2
3
$ ping -w 120 api.sgroup.qq.com
PING ins-npwk3wle.ias.tencent-cloud.net (101.91.19.174)
→ 41 packets transmitted, 41 received, 0% packet loss ✅
同一个域名,换了一个 IP,马上 0% 丢包? 这说明 api.sgroup.qq.com 背后不止一台服务器。DNS 轮询每次可能返回不同的 IP。
我又多跑了几次 ping,看看还会解析到什么地址。反复试下来,目前发现了三个不同的 IP:
| IP | 丢包率 | RTT |
|---|---|---|
| 175.27.13.145 | 76% 🔴 | 4.6ms |
| 101.91.19.174 | 0% ✅ | 21.5ms |
| 61.151.231.145 | 0% ✅ | 27.2ms |
通的时候 RTT 只有 4.6ms,但 10 个包里只回来 2~3 个——这是典型的 中间路由设备选择性丢包,而不是服务器本身挂了。
结论:api.sgroup.qq.com 的 DNS 轮询返回三个 IP,其中一个(175.27.13.145)有 76% 的 ICMP 丢包,其余两个完全正常。
间歇性发作的原因
1
2
3
4
DNS 轮询:
第一次 → 175.27.13.145 → ❌ 76% 丢包 → TLS 超时
第二次 → 101.91.19.174 → ✅ 0% 丢包 → 正常
第三次 → 61.151.231.145 → ✅ 0% 丢包 → 正常
这就是为什么问题一会儿好一会儿坏——取决于 DNS 轮询到了哪个 IP。运气好命中正常 IP,服务秒通;运气不好命中坏 IP,TLS 握手超时 10 秒。
我之前还做过整夜监控(653 次测试,6.5 小时),结果成功率在 25%~96.8% 之间大幅波动。现在想来,其实就是 DNS 轮询到坏 IP 的比例在变化罢了——根本不是”中间设备间歇性抽风”。
解决方案
知道了根因,解决就简单了。
临时方案:修改 /etc/hosts
把 api.sgroup.qq.com 固定指向正常的 IP 即可:
1
101.91.19.174 api.sgroup.qq.com
改完后服务立刻恢复,QQ Bot 再也没有发生过 TLS 握手超时。
但这不是长久之计——腾讯如果轮换 IP,101.91.19.174 可能某天就失效了。不过对于个人项目来说,撑几个月问题不大。
给腾讯云提工单
我也给腾讯云提交了工单,附上了完整的 ping 日志和 TLS 握手测试数据,请求排查 175.27.13.145 的高丢包问题,或者将其从 DNS 轮询中摘除。
关键知识:TCP 三次握手 vs TLS 握手
虽然这篇文章讲的是一次实际排障,但过程中的 TCP/TLS 知识值得单独拎出来讲清楚。
TCP 三次握手(传输层)
当你的程序调用 socket.connect() 时,操作系统内核会帮你完成三次握手:
1
2
3
4
5
6
7
8
9
客户端 服务器
| SYN (seq=x) |
| ──────────────────────────> | ① 客户端:喂,我要连你
| SYN+ACK (seq=y, ack=x+1) |
| <────────────────────────── | ② 服务器:收到,我能连
| ACK (ack=y+1) |
| ──────────────────────────> | ③ 客户端:好的,连上了
|
▼ 连接建立成功 ✅
关键特点:
- 在操作系统内核中完成,纯内核态,不涉及用户态程序
- 不涉及证书、不涉及加密、不涉及任何业务逻辑
- 耗时极短,通常 0.00x~0.0x 秒
- 只要 IP 可达、端口开放,基本秒通
TLS 握手(安全层)
TCP 连接建立好之后,在 TCP 的通道上再走一套流程,目的是加密通信。这完全在用户态完成,由 SSL 库(如 OpenSSL)实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
客户端 服务器
| ClientHello |
| (TLS版本, 密码套件列表) |
| ──────────────────────────> | ① 客户端:我要加密,支持这些算法
| ServerHello + Certificate |
| (选定算法 + 服务器证书) |
| <────────────────────────── | ② 服务器:用AES-GCM,这是我的证书
| 密钥交换 |
| ──────────────────────────> | ③ 客户端:验证证书,生成密钥
| Finished |
| <────────────────────────── | ④ 服务器:密钥确认,开始加密
|
▼ 安全通道建立 ✅
关键特点:
- 在用户态完成,涉及 SSL 库的复杂运算
- 需要证书验证、加密算法协商、密钥交换
- 涉及多个网络往返(RTT)
- 正常耗时 0.0x~0.x 秒
- 底层依赖 TCP 连接,如果 TCP 都不通,TLS 就更不可能通
为什么要同时用 ping 和 curl 来排查
这次排障让我深刻理解了一个道理:不同的工具测的是不同层面的东西。
| 工具 | 测什么 | 排查意义 |
|---|---|---|
ping |
ICMP 协议,网络层连通性 | 排查路由丢包 |
curl |
HTTP over TCP/TLS,应用层 | 验证完整业务链路 |
mtr |
ICMP 逐跳路径 | 定位哪一跳丢包 |
| Python socket 直连 | TCP 连接(不加密) | 隔离 TLS 的影响 |
如果 DNS 轮询到了坏 IP,所有工具都会挂——ping、curl、socket 全都不通。但如果 DNS 轮询到了好 IP,所有工具都正常。这就是”间歇性”问题的本质——不是网络本身波动,而是你每次请求去到的服务器不一样。
总结
这次排查最大的教训是:别预设根因,先用最简单的工具试一下。
- ❌ 直觉一:版本升级有 Bug(看日志排除)
- ❌ 直觉二:模型推理慢(对比测试排除)
- ❌ 直觉三:中间设备丢 ClientHello 包(似乎合理但错了)
- ✅ 真相:DNS 轮询到一个 76% 丢包的后端 IP
如果我早两个小时跑 ping,就不用做那么多复杂的 TLS 握手套接字测试了。
另一个教训是:跨云架构中 DNS 轮询是一个隐性风险点。 后端某台服务器出问题时,客户端不会收到错误提示,而是每隔几次请求随机被路由到故障节点上。这种问题最难排查——因为日志里没有任何关于”这次解析到哪个 IP”的记录,你看到的只是”有时候超时”。
顺便说一句,如果你也想跑类似测试,这个 Python 脚本纯标准库,无需 pip install,任何 Linux 服务器上都能直接用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/usr/bin/env python3
"""TLS 握手监控 + DNS 解析对比"""
import socket, ssl, time
def test_ip(ip, hostname):
t0 = time.time()
try:
sock = socket.socket()
sock.settimeout(10)
sock.connect((ip, 443))
ctx = ssl.create_default_context()
tls = ctx.wrap_socket(sock, server_hostname=hostname)
tls.close()
return f"✅ {time.time()-t0:.3f}s"
except Exception as e:
return f"❌ {time.time()-t0:.1f}s {type(e).__name__}"
host = "api.sgroup.qq.com"
print(f"DNS 解析 {host}:")
ips = [ip[4][0] for ip in socket.getaddrinfo(host, 443)]
for ip in ips:
print(f" {ip}: {test_ip(ip, host)}")
运行一次就能看到当前 DNS 解析到了哪些 IP,以及哪些是健康的。