• 主页
  • 架构
  • 编程语言
  • 数据存储
  • 网络
  • VMware
  • 服务器
  • 组网
  • AI
  • 算法系列
  • 设计模式
  • 读书笔记
  • 思考
  • 工具
  • 其它技术

  • 主页
  • 架构
  • 编程语言
  • 数据存储
  • 网络
  • VMware
  • 服务器
  • 组网
  • AI
  • 算法系列
  • 设计模式
  • 读书笔记
  • 思考
  • 工具
  • 其它技术

net/http: TLS handshake timeout 问题

2024-12-11

最近系统偶现”net/http: TLS handshake timeout“,而且都集中在同一个机房,这个报错还是第一次见,产生的原因和解决的方案都比较有意思。

现场

报错的信息为:

1
Error sending request:%!(EXTRA *url.Error=Get "https://****//test_image.jpg?lk3s=50ccb0c5&x-expires=1733490979%3D": net/http: TLS handshake timeout)

报错的位置为resp, err := client.Do(req):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
func GetImageContent(ctx context.Context, imageURL string) (string, oc_error.Error) {
// Create an HTTP client
client := &http.Client{}

// Create a new request using http
req, err := http.NewRequest("GET", imageURL, nil)
if err != nil {
logs.CtxError(ctx, "Error creating request:", err)
return "", config.SystemError
}

// Send the request via a client
resp, err := client.Do(req)
if err != nil {
logs.CtxError(ctx, "Error sending request:", err)
return "", config.SystemError
}
defer resp.Body.Close()

// Check if the request was successful
if resp.StatusCode != http.StatusOK {
logs.CtxError(ctx, "Error: Non-200 HTTP status code:", err)
return "", config.SystemError
}

body, err := ioutil.ReadAll(resp.Body)
if err != nil {
logs.CtxError(ctx, "ReadAll error %+v", err)
return "", config.SystemError
}
return string(body), nil
}

线索

通过查看日志,发现从请求开始到报错,经历了10s。为什么是10s呢?net/http包的默认TLSHandshakeTimeout超时时间是10s。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// DefaultTransport is the default implementation of Transport and is
// used by DefaultClient. It establishes network connections as needed
// and caches them for reuse by subsequent calls. It uses HTTP proxies
// as directed by the environment variables HTTP_PROXY, HTTPS_PROXY
// and NO_PROXY (or the lowercase versions thereof).
var DefaultTransport RoundTripper = &Transport{
Proxy: ProxyFromEnvironment,
DialContext: defaultTransportDialContext(&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}),
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}

大家一般不配置,但如果想配置的话,可以这么写

1
2
3
4
5
6
7
8
9
10
11
c := &http.Client{  
Transport: &Transport{
Dial: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
TLSHandshakeTimeout: 10 * time.Second,
ResponseHeaderTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
}

TLS handshake过程

以前写过HTTPS连接过程,这里面详细描述了TLS的执行过程。TLS过程中,发送端和接收端都需要给双方提供一些信息,当然也需要用到证书。

图片

所以产生的原因可能有三处,一个是运行我程序的容器有问题(可能性比较小),一个是网络(内部网络或外部网络)上有问题(可能性比较大)。

先去找了容器的同学,他们反馈最近没有更新,而且感觉真的是容器的问题,大概率是百分百出问题。

然后去找CDN的同学,初步判断是外部网络的问题。

追查

其实这个机房报错概率还是比较高的,在5%~10%之间。

找问题IP

为了完整复现,登录容器,执行

1
curl -v https://****

使用 curl --verbose或curl -v 命令可以详细显示 HTTP 请求和响应的过程

image-20241206174720918

我们的域名对应多个IP地址,多次执行curl,发现部分IP地址确实会慢。

抓包

开两个窗口,一个窗口执行如下命令,使用tcpdump抓包,其中的ip是上面查到的有问题的ip,将抓包结果写入指定文件

1
tcpdump -i any host  ip1  or   host ip2  or  ip3   -w /home/if9.pcap

开另一个窗口,执行curl,一直执行到对应的ip确实出现握手失败的时候。

导出数据

把容器里的文件下载到本地,超复杂,但公司的同学真的是什么都经历过了,愣是走出了一条路。

需要使用item2,配置sz指令,可以参考 https://github.com/aikuyun/iterm2-zmodem。

分析

下载好抓到的包,可以使用WireShark进行分析,你看,10s的位置就找到了。

image-20241206175958438

解决

后续CDN的同学找了厂商,根据厂商反馈是厂商节点问题,对应的节点性能有点减弱,从服务链路进行剔除。针对全网是否还有类似的节点,厂商继续全网检查看看,有类似的及时处理掉。

资料

  1. golang net/http 超时机制完全手册

扫一扫,分享到微信

微信分享二维码
论语第五篇-公冶长
团队分工
© 2025 John Doe
Hexo Theme Yilia by Litten