http.Response.Body的复制导致内存使用量大增。

huangapple go评论82阅读模式
英文:

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.

huangapple
  • 本文由 发表于 2015年9月8日 13:47:33
  • 转载请务必保留本文链接:https://go.coder-hub.com/32450094.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定