英文:
http.Response.Body copying leads to large memory usage increase
问题
所以我正在尝试从http.Response中获取响应体,进行一些操作,然后再设置回去。这是我第一次尝试在不耗尽http.Response的情况下获取它:
bodyBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
// 错误处理
} else {
cachedBody := string(bodyBytes)
// 将io.ReadCloser恢复到原始状态
// 这会导致内存使用量大幅增加。
resp.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
}
如果我发送500个请求,每个请求的响应大小为1mb(一个大的JSON片段,但也可以是任何格式,不仅限于JSON),服务器的内存使用量会增加到约400mb,并且不会降下来。这正常吗?上面的代码有什么问题吗?我是否漏掉了释放内存的步骤?
defer resp.Body.Close()
没有效果。
谢谢!
编辑1
这里有一个简化版本的代理的gist,包括建议的关闭调用。如果localhost:3000上的服务器(代理的目标)返回一个大的响应,内存使用量将迅速增加。在我的情况下,我返回一个1mb的JSON文件,并通过go代理发送500个请求会使内存使用量增加到约400mb。
go代理:
https://gist.github.com/marbemac/300c4c376171cbd27cc3
一个简单的Node服务器,在同一目录下返回一个名为large_response.json的文件。
https://gist.github.com/marbemac/55aaa78f5858484d33f6
英文:
So I'm trying to get the response body out of http.Response, to do some manipulation, and then set it back. This is my first attempt at getting it out without draining the http.Response:
bodyBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
// err
} else {
cachedBody := string(bodyBytes)
// Restore the io.ReadCloser to its original state
// This is causing huge increases in memory usage.
resp.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
}
If I send 500 requests through, with a 1mb response size (a large piece of JSON, but it could be any format, not only JSON), the server memory usage goes up to ~400mb, and doesn't come back down. Is this normal? Is there something wrong with the above code? Am I missing something to release the memory?
defer resp.Body.Close() has no effect.
Thanks!
Edit 1
Here's a gist with a simple version of the proxy, including the suggested close call. If the server at localhost:3000 (where it's proxying to) returns a large response, memory usage will quickly increase. In my case, I'm returning a 1mb json file, and sending 500 requests through the go proxy increases memory usage to around 400mb.
The go proxy:
https://gist.github.com/marbemac/300c4c376171cbd27cc3
A simple node server that returns a file in the same directory called large_response.json.
https://gist.github.com/marbemac/55aaa78f5858484d33f6
答案1
得分: 3
Body在使用后必须关闭。请参考这个链接。
请注意,在将resp.Body
重新赋值后,需要调用defer resp.Body.Close()
。
英文:
The Body must be closed after use. Refer to this
Notice that the defer resp.Body.Close()
is invoked after you reassign the resp.Body
with new value.
答案2
得分: 2
ioutil.NopCloser
函数使Close()
方法不执行任何操作。如果你使用defer
延迟关闭,实际上你并没有真正关闭它。应该在读取完毕后立即关闭。
bodyBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
// 错误处理
}
resp.Body.Close()
cachedBody := string(bodyBytes)
// 在这之后,Close()方法不会有任何作用
resp.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
此外,在Go语言中,通常可以忽略else
语句,因为你应该在上面的if
语句中返回错误或处理错误。
在这种情况下,使用sync.Pool
会很方便,因为你可以重复使用大型缓冲区。可以查看net/http
包中的使用示例。
英文:
ioutil.NopCloser
makes Close()
do nothing. You're never actually closing it if you defer the close. Close right after reading instead.
bodyBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
// err
}
resp.Body.Close()
cachedBody := string(bodyBytes)
// Close() does nothing after this
resp.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
Also in Go you're typically able to ignore that else
because you should probably be returning the error or handling it in the if
above it.
Things like this are times when it would be nice to have a sync.Pool
as well, since you keep recycling huge buffers, check how it is used in the net/http
package for an example.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论