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
相关产品推荐
相关产品推荐

