多线程如何影响HTTP长连接保持(keep-alive)?

8
var (
    httpClient *http.Client
)

const (
    MaxIdleConnections int = 20
    RequestTimeout     int = 5
)

// init HTTPClient
func init() {
    client := &http.Client{
        Transport: &http.Transport{
            MaxIdleConnsPerHost: MaxIdleConnections,
        },
        Timeout: time.Duration(RequestTimeout) * time.Second,
    }

    return client
}

func makeRequest() {
    var endPoint string = "https://localhost:8080/doSomething"

    req, err := http.NewRequest("GET", ....)
    
    response, err := httpClient.Do(req)
    if err != nil && response == nil {
        log.Fatalf("Error sending request to API endpoint. %+v", err)
    } else {
        // Close the connection to reuse it
        defer response.Body.Close()
        body, err := ioutil.ReadAll(response.Body)
        if err != nil {
            log.Fatalf("Couldn't parse response body. %+v", err)
        }
        
        log.Println("Response Body:", string(body))
    }
}

我在Go中有以下代码。Go使用http-keep-alive连接。因此,据我所知,httpClient.Do(req)不会创建新连接,因为golang使用默认的持久连接。

  1. 据我了解,HTTP持久连接一次只能发送一个请求,即第二个请求只能在第一个响应之后发出。但是如果多个线程调用 makeRequest()会发生什么?httpClient.Do(req)是否会在前一个请求得到响应之前发送另一个请求?

  2. 我假设服务器会超时关闭客户端建立的任何keep-alive连接。如果服务器超时,那么下一次调用httpClient.Do(req)时,它会建立一个新的连接吗?


1
请参阅 https://godoc.org/net/http#Transport.MaxConnsPerHost 的文档。 - Charlie Tumahai
3
你当前的代码无法编译。init如何能够返回东西? - Mayank Patel
2
“第二个请求只能在第一个响应之后进行。” 如果所有现有连接仍在使用中,则客户端将打开另一个连接,最多达到某个配置的最大值。只有当达到此限制时,新请求才必须等待连接变为空闲状态。 - Peter
@Peter,我没有使用任何连接池。我只依赖Client.Do来打开一个新的连接。那么你是在暗示Client.Do会打开一个新的连接,尽管Go语言使用keep-alive吗? - JavaDeveloper
1
你正在使用 http.Transport,它实现了一个连接池。"默认情况下,Transport 会缓存连接以供将来重用。" - Peter
对于(1),HTTP/1.1 还有 HTTP 管线化 和 HTTP/2 的多路复用。它们允许在同一连接上进行多个并发请求。这不会改变答案,但了解这些是很好的。 - kichik
2个回答

7
一个 http.Client 有一个 Transport,它委托了许多低级别的请求细节。您可以通过为客户端提供自定义的 Transport 来更改几乎所有内容。本回答的其余部分将在很大程度上假定您正在使用 http.DefaultClient 或至少带有 http.DefaultTransport 的客户端。
在进行新请求时,如果适当服务器的空闲连接可用,则传输将使用它。
如果没有空闲连接(因为从未有过连接,或者因为其他 Goroutine 正在使用全部连接,或者因为服务器关闭了连接,或者出现了其他错误),则传输将考虑建立一个新连接,受 MaxConnsPerHost(默认值:没有限制)限制。如果超过了 MaxConnsPerHost,则该请求将阻塞,直到现有请求完成并且连接可用。否则,将为此请求建立新连接。
请求完成后,客户端将缓存该连接以供以后使用(受 MaxIdleConnsMaxIdleConnsPerHost 限制;DefaultTransport 全局拥有100个空闲连接的限制,每个主机没有限制)。
如果空闲连接未用于发出请求,则在 IdleConnTimeout 后关闭空闲连接;对于 DefaultTransport,该限制为90秒。
这就意味着,默认情况下,Go 将建立足够的连接以满足并行性(在您可以调整的某些限制下),但它也将尽可能多地重复使用保持活动状态的连接,通过维护一组空闲连接。

感谢您的回答。假设连接池中只有一个连接,并且该连接保持活动状态,当多个线程尝试使用该连接时会发生什么?它会阻塞直到前一个请求完成吗?保持活动状态与流水线处理不同。 - JavaDeveloper
@Java开发者,这就是我的答案。 - hobbs

1
根据您的代码,它不会影响HTTP保持活动连接。您正在使用全局httpClient,如果在多个线程中调用,则不会创建新连接,正如您所期望的那样。此外,它在关闭之前读取了响应体response.Body。如果提供的response.Bodyio.Closer,则请求后它将被关闭。

是的,但如果多个线程调用同一个连接,它会阻塞直到第一个响应吗? - JavaDeveloper
1
根据 httpClient.Do(re *Request) 文档,如果请求超时或被取消,则 url.Error 值的 timeout 方法将报告为 true。请求连接会缓存以供将来重用,并且该行为将根据 Tranport 文档使用 Transport 的 CloseIdleConnections 方法、MaxIdleConnsPerHost 和 DisableKeepAlives 字段进行管理。对于我来说,它将缓存连接请求,直到第一个响应。 - Adriel Artiza

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