You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Golang并发HTTP请求时如何实现连接复用?

问题描述

我希望在并发场景下通过单个HTTP连接发起多个请求,但当前代码会创建4-5个连接;而不使用并发时只会创建1个连接。

package main

import (
    "fmt"
    "net/http"
    "net/url"
    "sync"
    "time"
)

func main() {
    proxyURL, err := url.Parse("http://localhost:9099")//In the traffic sniffer tool I see this program makes 5 requests
    if err != nil {
        fmt.Println(err)
        return
    }
    
    var wg sync.WaitGroup
    client := &http.Client{
        Transport: &http.Transport{
            MaxIdleConns:        10,
            IdleConnTimeout:     90 * time.Second,
            DisableKeepAlives:   false,
            Proxy: http.ProxyURL(proxyURL),
        },}

    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func(index int) {
            defer wg.Done()
            resp, err := client.Get("https://www.httpbin.org/get")
            if err != nil {
                fmt.Println(err)
                return
            }
            err = resp.Body.Close()
            if err != nil {
                fmt.Println(err)
                return 
            }
            fmt.Println(index)
        }(i)
    }

    wg.Wait()
    fmt.Println("done")
}

原因分析

默认的http.Transport中MaxConnsPerHost参数默认值为0,意味着不对目标主机的并发连接数做限制。当同时启动5个goroutine发起请求时,此时还没有可用的空闲持久连接,Transport会为每个并发请求创建新连接,因此会出现多个连接的情况。

解决方案:限制单主机并发连接数

只需在http.Transport中配置MaxConnsPerHost: 1,强制限制目标主机的并发连接数为1,所有请求会排队复用这个唯一的连接:

package main

import (
    "fmt"
    "net/http"
    "net/url"
    "sync"
    "time"
)

func main() {
    proxyURL, err := url.Parse("http://localhost:9099")
    if err != nil {
        fmt.Println(err)
        return
    }
    
    var wg sync.WaitGroup
    client := &http.Client{
        Transport: &http.Transport{
            MaxIdleConns:        10,
            IdleConnTimeout:     90 * time.Second,
            DisableKeepAlives:   false,
            Proxy: http.ProxyURL(proxyURL),
            MaxConnsPerHost:     1, // 新增参数限制单主机并发连接数
        },}

    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func(index int) {
            defer wg.Done()
            resp, err := client.Get("https://www.httpbin.org/get")
            if err != nil {
                fmt.Println(err)
                return
            }
            defer resp.Body.Close() // 改用defer确保连接关闭
            fmt.Println(index)
        }(i)
    }

    wg.Wait()
    fmt.Println("done")
}

更高效的替代方案:提前建立连接

当请求量较大时,提前建立连接的方式比设置MaxConnsPerHost: 1更快——在启动goroutine前先发起一个预热请求,让Transport创建并持有持久连接,后续的并发请求可以直接复用该连接,无需排队等待连接释放:

package main

import (
    "fmt"
    "net/http"
    "net/url"
    "sync"
    "time"
)

func main() {
    proxyURL, err := url.Parse("http://localhost:9099")
    if err != nil {
        fmt.Println(err)
        return
    }
    
    var wg sync.WaitGroup
    client := &http.Client{
        Transport: &http.Transport{
            MaxIdleConns:        10,
            IdleConnTimeout:     90 * time.Second,
            DisableKeepAlives:   false,
            Proxy: http.ProxyURL(proxyURL),
        },}

    // 预热连接:提前发起一个请求,让Transport创建持久连接
    resp, err := client.Get("https://www.httpbin.org/get")
    if err != nil {
        fmt.Println(err)
        return
    }
    resp.Body.Close()

    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func(index int) {
            defer wg.Done()
            resp, err := client.Get("https://www.httpbin.org/get")
            if err != nil {
                fmt.Println(err)
                return
            }
            defer resp.Body.Close()
            fmt.Println(index)
        }(i)
    }

    wg.Wait()
    fmt.Println("done")
}

内容的提问来源于stack exchange,提问作者HenTaku

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 22:03:11