最近系统偶现”net/http: TLS handshake timeout“,而且都集中在同一个机房,这个报错还是第一次见,产生的原因和解决的方案都比较有意思。
现场
报错的信息为:
vbscript
代码解读
复制代码
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):
go
代码解读
复制代码
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。
go
代码解读
复制代码
// 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, }
大家一般不配置,但如果想配置的话,可以这么写
go
代码解读
复制代码
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
为了完整复现,登录容器,执行
shell
代码解读
复制代码
curl -v https://****
使用 curl --verbose或curl -v
命令可以详细显示 HTTP 请求和响应的过程
我们的域名对应多个IP地址,多次执行curl,发现部分IP地址确实会慢。
抓包
开两个窗口,一个窗口执行如下命令,使用tcpdump抓包,其中的ip是上面查到的有问题的ip,将抓包结果写入指定文件
arduino
代码解读
复制代码
tcpdump -i any host ip1 or host ip2 or ip3 -w /home/if9.pcap
开另一个窗口,执行curl,一直执行到对应的ip确实出现握手失败的时候。
导出数据
把容器里的文件下载到本地,超复杂,但公司的同学真的是什么都经历过了,愣是走出了一条路。
需要使用item2,配置sz指令,
下载好抓到的包,可以使用WireShark进行分析,你看,10s的位置就找到了。
解决
后续CDN的同学找了厂商,根据厂商反馈是厂商节点问题,对应的节点性能有点减弱,从服务链路进行剔除。针对全网是否还有类似的节点,厂商继续全网检查看看,有类似的及时处理掉。