Golang中net/http的keep alive功能失效问题解决方案
Golang中net/http的keep alive功能失效问题解决方案
我编写了两个测试单元。在下面的测试单元中,keep alive 确实生效了。
func BenchmarkNetHTTPClientBase2(b *testing.B) {
client := http.Client{Transport: http.DefaultTransport}
for i := 0; i < b.N; i++ {
pingReq, _ := http.NewRequestWithContext(context.Background(), "GET", "http://127.0.0.1:7777/ping", nil)
pingRes, err := client.Do(pingReq)
if err != nil {
b.Error(err)
} else {
_, err = ioutil.ReadAll(pingRes.Body)
if err != nil {
b.Error(err)
}
pingRes.Body.Close()
}
}
}
但是当我做了一些修改,添加了 RunParallel 后,keep alive 就不起作用了。
func BenchmarkNetHTTPClientBase1(b *testing.B) {
client := http.Client{Transport: http.DefaultTransport}
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
pingReq, _ := http.NewRequestWithContext(context.Background(), "GET", "http://127.0.0.1:7777/ping", nil)
pingRes, err := client.Do(pingReq)
if err != nil {
b.Error(err)
} else {
_, err = ioutil.ReadAll(pingRes.Body)
if err != nil {
b.Error(err)
}
pingRes.Body.Close()
}
}
})
}
有什么想法吗?谢谢。
更多关于Golang中net/http的keep alive功能失效问题解决方案的实战教程也可以访问 https://www.itying.com/category-94-b0.html
1 回复
更多关于Golang中net/http的keep alive功能失效问题解决方案的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
问题在于 RunParallel 会并发执行多个 goroutine,而 http.DefaultTransport 是全局共享的。当多个 goroutine 并发使用同一个 http.Client 时,连接池的竞争可能导致 keep-alive 行为异常。
以下是解决方案:
- 为每个 goroutine 创建独立的 Transport(推荐):
func BenchmarkNetHTTPClientBase1(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
// 每个 goroutine 使用独立的 Transport
tr := &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 100,
IdleConnTimeout: 90 * time.Second,
}
client := &http.Client{Transport: tr}
for pb.Next() {
pingReq, _ := http.NewRequestWithContext(context.Background(),
"GET", "http://127.0.0.1:7777/ping", nil)
pingRes, err := client.Do(pingReq)
if err != nil {
b.Error(err)
} else {
_, err = io.ReadAll(pingRes.Body)
if err != nil {
b.Error(err)
}
pingRes.Body.Close()
}
}
})
}
- 使用 sync.Pool 重用 Transport(性能优化):
func BenchmarkNetHTTPClientBase1(b *testing.B) {
transportPool := &sync.Pool{
New: func() interface{} {
return &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 100,
IdleConnTimeout: 90 * time.Second,
}
},
}
b.RunParallel(func(pb *testing.PB) {
tr := transportPool.Get().(*http.Transport)
defer transportPool.Put(tr)
client := &http.Client{Transport: tr}
for pb.Next() {
pingReq, _ := http.NewRequestWithContext(context.Background(),
"GET", "http://127.0.0.1:7777/ping", nil)
pingRes, err := client.Do(pingReq)
if err != nil {
b.Error(err)
} else {
_, err = io.ReadAll(pingRes.Body)
if err != nil {
b.Error(err)
}
pingRes.Body.Close()
}
}
})
}
- 调整全局 Transport 参数:
func init() {
http.DefaultTransport.(*http.Transport).MaxIdleConnsPerHost = 100
}
func BenchmarkNetHTTPClientBase1(b *testing.B) {
client := &http.Client{Transport: http.DefaultTransport}
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
pingReq, _ := http.NewRequestWithContext(context.Background(),
"GET", "http://127.0.0.1:7777/ping", nil)
pingRes, err := client.Do(pingReq)
if err != nil {
b.Error(err)
} else {
_, err = io.ReadAll(pingRes.Body)
if err != nil {
b.Error(err)
}
pingRes.Body.Close()
}
}
})
}
关键点:
RunParallel默认使用GOMAXPROCS个 goroutine 并发执行- 默认的
MaxIdleConnsPerHost是 2,并发 goroutine 超过这个数时,多余的连接会被关闭 - 使用
io.ReadAll替代已废弃的ioutil.ReadAll - 确保正确关闭响应体以释放连接回连接池
第一种方案是最直接有效的解决方案,它为每个并发 goroutine 提供了独立的连接池,避免了竞争条件。

