GoLang net/http持续请求时内存占用异常增长问题
问题原因及解决办法
一、核心问题分析
1. 错误处理逻辑错误
在sendRequest函数中,当client.Do(req)返回错误时,resp大概率为nil,此时执行defer resp.Body.Close()会直接触发空指针panic,且该defer语句的位置完全错误——defer应在成功获取resp后立即声明,而非放在错误分支中。
2. 内存持续增长的根源
复用http.Client的思路是正确的,但默认的http.Transport配置会导致连接池内的空闲连接无法及时回收:
- 默认
IdleConnTimeout为0,意味着空闲连接会永久留存于连接池,循环请求会不断积累空闲连接,占用内存。 - 默认
MaxIdleConns和MaxIdleConnsPerHost值较高,进一步加剧了空闲连接的留存规模。
二、修复方案
- 修正错误处理逻辑:将
defer resp.Body.Close()移至client.Do(req)成功之后,确保仅在resp非空时执行关闭操作,同时移除错误分支内的无效defer。 - 优化
http.Transport配置:添加空闲连接超时、限制连接池大小,让闲置连接能被自动回收。 - (可选)替换已废弃API:Go 1.16及以后版本推荐使用
io.ReadAll替代ioutil.ReadAll。
三、修复后的完整代码
package main import ( "bytes" "encoding/json" "io" "log" "net/http" "time" ) func httpClient() *http.Client { transport := &http.Transport{ DisableKeepAlives: false, // 设置空闲连接超时,超时后自动关闭闲置连接 IdleConnTimeout: 30 * time.Second, // 限制全局最大空闲连接数 MaxIdleConns: 10, // 限制每个主机的最大空闲连接数 MaxIdleConnsPerHost: 5, } client := &http.Client{Timeout: 60 * time.Second, Transport: transport} return client } func sendRequest(client *http.Client, method string) []byte { endpoint := "https://httpbin.org/post" values := map[string]string{"foo": "baz"} jsonData, err := json.Marshal(values) if err != nil { log.Fatalf("JSON序列化失败: %+v", err) } req, err := http.NewRequest(method, endpoint, bytes.NewBuffer(jsonData)) if err != nil { log.Fatalf("创建请求失败: %+v", err) } resp, err := client.Do(req) if err != nil { log.Fatalf("发送请求失败: %+v", err) } // 成功获取响应后立即声明defer,确保后续逻辑无论如何都会关闭响应体 defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { log.Fatalf("读取响应体失败: %+v", err) } return body } func main() { c := httpClient() for i := 1; i <= 60; i++ { response := sendRequest(c, http.MethodPost) log.Println("响应内容:", string(response)) response = nil time.Sleep(time.Second * 1) } }
四、额外说明
- 调整
IdleConnTimeout、MaxIdleConns等值时,可根据实际请求频率灵活配置,平衡连接复用效率与内存占用。 - 若程序需长期运行,可借助Go的
pprof工具做内存分析,确认内存占用问题是否完全解决。
内容的提问来源于stack exchange,提问作者Ahsan
相关产品推荐
相关产品推荐

