关闭Go http.Client的连接池

20
为了测试目的,我正在尝试在Go中创建一个net/http.Client,并禁用连接池。我的目标是,在每个HTTP/1.x请求时,都会建立到地址的新TCP连接。
目前我有:
        c = &http.Client{
            Transport: &http.Transport{
                DialContext: (&net.Dialer{
                    Timeout:   5 * time.Second,
                    KeepAlive: 5 * time.Second,
                }).DialContext,
                TLSHandshakeTimeout:   5 * time.Second,
                ResponseHeaderTimeout: 5 * time.Second,
                ExpectContinueTimeout: 1 * time.Second,
            },
        }

有什么建议可以调整这个吗?

我发现如果设置c.Transport.MaxIdleConns = 1,可能会起作用,但我不确定这是否仍允许使用1个+1个空闲(共2个)TCP连接:

    // MaxIdleConns controls the maximum number of idle (keep-alive)
    // connections across all hosts. Zero means no limit.
    MaxIdleConns int

同样地,c.Dialer.KeepAlive = -1 似乎也能实现这个目的:

    // KeepAlive specifies the keep-alive period for an active
    // network connection.
    // If zero, keep-alives are enabled if supported by the protocol
    // and operating system. Network protocols or operating systems
    // that do not support keep-alives ignore this field.
    // If negative, keep-alives are disabled.

但我不确定TCP连接+ Keep-Alive + HTTP的行为。

另一种方法是尽快终止空闲的TCP连接,因此我设置了c.Transport.IdleConnTimeout = 1*time.Nanosecond

当我这样做时,我的Client.Do()现在偶尔会返回错误:

tls: use of closed connection

我怀疑这是 Go 标准库的问题(可能是竞争条件),它使用了一个应该已经从池中取出的连接。


你是否只需要一个客户端?最简单的方法是创建一个函数,该函数返回一个新的客户端对象供您使用。 - Jessie
@Jesse,如果你只做那个,那么你会泄漏开放的TCP连接。 - JimB
为什么不直接关闭连接,不用担心池呢?您尝试过使用 Request.Close(或设置 Connection: close 标头)吗? - JimB
@JimB 如果这只是为了测试目的,那可能不是一个问题。 - Jessie
任何一种都可以。能否添加为答案? - ahmet alp balkan
3个回答

11

在函数Transport.tryPutIdleConn中将连接添加到池中。如果Transport.DisableKeepAlives为true或Transport.MaxIdleConnsPerHost小于零,则不会将连接添加到池中。

设置任一值均可禁用池化。当DisableKeepAlives为true时,传输添加Connection: close请求标头。这可能是不是您想要测试的,具体取决于您的需求。

以下是如何设置DisableKeepAlives:

t := http.DefaultTransport.(*http.Transport).Clone()
t.DisableKeepAlives = true
c := &http.Client{Transport: t}

这里是如何设置MaxIdleConnsPerHost:

在Playground上运行DisableKeepAlives = true的演示

t := http.DefaultTransport.(*http.Transport).Clone()
t.MaxIdleConnsPerHost = -1
c := &http.Client{Transport: t}

在playground上运行MaxIdleConnsPerHost = -1的演示

以上代码克隆了默认传输以确保使用默认传输选项。如果您明确想要问题中的选项,则使用

    c = &http.Client{
        Transport: &http.Transport{
            DialContext: (&net.Dialer{
                Timeout:   5 * time.Second,
                KeepAlive: 5 * time.Second,
            }).DialContext,
            TLSHandshakeTimeout:   5 * time.Second,
            ResponseHeaderTimeout: 5 * time.Second,
            ExpectContinueTimeout: 1 * time.Second,
            DisableKeepAlives: true,
        },
    }

或者

    c = &http.Client{
        Transport: &http.Transport{
            DialContext: (&net.Dialer{
                Timeout:   5 * time.Second,
                KeepAlive: 5 * time.Second,
            }).DialContext,
            TLSHandshakeTimeout:   5 * time.Second,
            ResponseHeaderTimeout: 5 * time.Second,
            ExpectContinueTimeout: 1 * time.Second,
            MaxIdleConnsPerHost: -1,
        },
    }

MaxIdleConnsPerHost不限制每个主机的活动连接数。 查看此Playground示例以进行演示

通过将Dialer.KeepAlive设置为-1,不会禁用连接池。请参见此答案以获得解释。


4

您需要将DisableKeepAlives设置为true,将MaxIdleConnsPerHost设置为-1。

根据文档:

// DisableKeepAlives, if true, disables HTTP keep-alives and
// will only use the connection to the server for a single
// HTTP request.

因此,您的客户端必须按照以下方式进行初始化

https://golang.org/src/net/http/transport.go,166行和187行。

c = &http.Client{
    Transport: &http.Transport{
        DialContext: (&net.Dialer{
            Timeout:   5 * time.Second,
        }).DialContext,
        TLSHandshakeTimeout:   5 * time.Second,
        ResponseHeaderTimeout: 5 * time.Second,
        ExpectContinueTimeout: 1 * time.Second,
        DisableKeepAlives: true,
        MaxIdleConnsPerHost: -1
    },
}

如果您使用的是 1.7 版本之前的 Go 版本,则需要消耗掉请求体的所有缓冲区,然后才能调用 request.Body.Close()。而如果您使用的是 1.7 及以上版本,则可以延迟关闭而无需进行任何额外的预防措施。
以下是一个禁用连接池但仍能执行并行请求的示例库: https://github.com/alessiosavi/Requests

你有关于1.7版本中使得消耗缓冲区变得不必要的改变的参考资料吗?我找不到任何相关的信息。 - undefined

0

http.Transport有一个名为MaxConnsPerHost的属性。

MaxConnsPerHost可选地限制每个主机的总连接数,包括拨号、活动和空闲状态下的连接。

它包括拨号、活动和空闲状态。


这并没有帮助,因为一旦我发出请求并建立了到 example.com 的连接,我需要关闭该连接,以便下一个请求可以从头开始。然而,设置 MaxConnsPerHost:1 仍会重用那个 TCP 连接。 - ahmet alp balkan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接