最近在对接微信接口时,遇到一个很诡异的问题:
偶尔会报 TLS 握手超时,但在宿主机 curl
是正常的。
这类“偶发性 + 网络相关”的问题最难排查,一开始我陷入了“是不是 SDK 有 bug?”的误区,直到我真正深入底层,才发现——原来是 MTU 惹的祸。
环境
在此之前大部分的生产环境我都是部署在公网上,这一次是部署在内网的虚拟化 VM 上。 我的部署环境是这样的:
- 宿主机是 Linux
- 应用程序跑在容器里(Docker)
- 使用微信 SDK 与微信服务器通信(HTTPS)
- 网络通过 bridge 桥接
- 有防火墙限制,但宿主机
curl
请求一切正常
问题场景是这样的:应用偶尔报错TLS handshake timeout
。不是所有请求都失败,只是偶尔出现,在宿主机执行 curl -v https://api.weixin.qq.com
正常,毫无问题。
我排查问题的思路
网络相关的超时问题,常见有几个方向:
- DNS 解析慢?
- 服务端网络抖动?
- SDK 实现问题?
- 网络丢包或 MTU 不匹配?
第一步:确认 DNS 没问题
我用 dig
和 curl -w
分析 DNS 解析时间,DNS 时间非常短,都是 10ms 以下,所以可以排除 DNS 问题。
第二步:用 curl 模拟请求
宿主机直接 curl
没问题:curl -v https://api.weixin.qq.com
TLS 握手也很快,毫无异常。
但问题是在容器中运行的服务出现超时。
那我就在容器内也跑一次 curl
:
curl -v https://api.weixin.qq.com
这时候就有趣了:
- 有时候会卡在
TLS handshake
- 有时候直接
timeout
于是可以断定——问题只出现在容器内部。
关键点:MTU 不一致导致握手失败
接下来,我开始怀疑是否存在网络层的问题。
于是我通过 ip link show
查看了宿主机和容器内的 MTU 设置。以宿主机为例,结果如下:
# 在宿主机执行
ip link show
结果是这样的:
...
2: ens3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1442 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
...
宿主机 MTU 只有 1442,而不是标准的 1500。
而 Docker 网络默认 MTU 是 1500 ,将 Docker 容器的 MTU 修改为 1442 后再次执行 curl
请求,TLS 握手变得稳定了。
为什么 MTU 会影响 TLS 握手?
TLS 握手初期会交换证书、加密参数等内容,其中证书包往往比较大。如果 MTU 设置过大,导致网络包超过了底层网络(比如 Docker overlay、VPN、bridge)实际支持的最大尺寸,就可能发生 IP 分片。
一旦需要分片,如果中间链路的防火墙丢弃分片,或者因 ICMP 被禁用而导致 PMTU(路径MTU发现)探测失败,数据包便会丢失,最终导致握手超时。这也解释了为什么问题是“偶尔”出现:并非所有TLS连接(例如会话复用的连接)的握手包都大到需要分片。
解决方案
确定原因后,解决方法就简单了:
- 把容器内的网卡 MTU 设置为 1442 或更小。
- 或者在 host 上配置,使 bridge 的 MTU 与物理网卡保持一致。
- 如果使用 Docker / Docker Compose,可以添加
--mtu
参数或在配置文件中指定:networks: default: driver: bridge driver_opts: com.docker.network.driver.mtu: "1442"
小结
这是一次非常典型的“网络层导致 TLS 握手失败”的问题,不是代码的问题,也不是 SDK 的锅。 从表面上看,是“偶发性超时”,但这次排查揭示了其背后的根源在于:
- 真正的根因是 MTU 不一致引起的数据包丢失。
- 而 TLS 握手对丢包非常敏感,一旦关键握手包丢失就会失败。
所以如果你也遇到 HTTPS 或 SDK 请求偶尔超时,一定要检查一下 MTU 设置,特别是在容器或桥接网络场景下。
此文档包含 AI 生成部分,如有纰漏请 @ 我