为什么Golang HTTP客户端的连接池大小不断增加



我基本上是在为一个庞大的域列表制作一个健康检查爬网程序。我有一个Golang脚本,它创建了大约256个例程,这些例程向域列表发出请求。我使用的是具有以下传输配置的同一客户端:

# init func
this.client = &http.Client{
Transport: &http.Transport{
ForceAttemptHTTP2:   true,
TLSHandshakeTimeout: TLSHandShakeTimeout,
TLSClientConfig:     &tls.Config{InsecureSkipVerify: true},
MaxConnsPerHost:     -1,
DisableKeepAlives:   true,
},
Timeout: RequestTimeout,
}
... 
# crawler func
req, err := http.NewRequestWithContext(this.ctx, "GET", opts.Url, nil)
if err != nil {
return nil, errors.Wrap(err, "failed to create request")
}
res, err := this.client.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
...

我运行了netstat -anp | wc -l,可以使用TIME_WAIT看到超过2000个连接。

http.Client的每个主机的goroutines默认数量为2。一个用于接收方,另一个用于发送方。因此,对于成千上万的域来说,这里可能有成千上万的goroutines。

由于DisableKeepAlives设置为true,因此当HTTP响应完成时,连接将关闭。TIME_WAIT是关闭连接后的正常TCP状态。

但是,Linux上TIME_WAIT状态的默认超时为60秒。大量的TIME_WAIT状态可能会导致服务器(如探测器/爬网程序(连接问题。


为了解决TIME_WAIT问题。SO_LINGER选项可能会有所帮助。它禁用默认的TCP延迟关闭行为,该行为在连接关闭时向对等方发送RST。并且它将删除TCP连接的TIME_wAIT状态。

更多的讨论可以在这里找到什么时候需要TCP选项SO_LINGER(0(?

样品

dialer := &net.Dialer{
Control: func(network, address string, conn syscall.RawConn) error {
var opterr error
if err := conn.Control(func(fd uintptr) {
l := &syscall.Linger{}
opterr = syscall.SetsockoptLinger(int(fd), unix.SOL_SOCKET, unix.SO_LINGER, l)
}); err != nil {
return err
}
return opterr
},
}
client := &http.Client{
Transport: &http.Transport{
DialContext: dialer.DialContext,
},
}

此外,这是EaseProbe中的另一个SO_LINGER用例。它是一个简单、独立和轻量级的工具,可以进行健康/状态检查。

最新更新