Go语言标准库中是否存在内存泄漏问题?

14

编写一个Go二进制文件实现http服务器:

package main

import (
    "net/http"
)

func main() {
    http.ListenAndServe(":8080", nil)
}

这个程序开始时大约占用850kb左右的内存。通过web浏览器向它发送一些请求,你会看到它迅速上升到1mb。如果你等待一段时间,你会发现它不会下降。现在使用Apache Bench(使用下面的脚本)对其进行压力测试,并观察你的内存使用持续增加。经过一段时间后,它最终会稳定在约8.2MB左右。

修改:似乎它并不会停在8.2,而是明显放慢了。它目前在9.2并且仍在上升。

简而言之,为什么会发生这种情况?我使用了这个shell脚本:

while [ true ]
do
    ab -n 1000 -c 100 http://127.0.0.1:8080/
    sleep 1
end

在尝试查找问题根源时,我已经尝试调整设置。我试过使用 r.Close = true 来防止 Keep-Alive 关闭连接。但似乎没有任何作用。

我最初在尝试确定自己编写的程序是否存在内存泄漏时发现了这个问题。它包含大量的http处理程序和I/O调用。在确认关闭了所有数据库连接后,我仍然发现内存使用率不断上升,程序稳定在约433 MB左右。

下面是Goenv的输出:

GOARCH="amd64"
GOBIN=""
GOCHAR="6"
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOOS="darwin"
GOPATH="/Users/mark/Documents/Programming/Go"
GORACE=""
GOROOT="/usr/local/go"
GOTOOLDIR="/usr/local/go/pkg/tool/darwin_amd64"
TERM="dumb"
CC="clang"
GOGCCFLAGS="-g -O2 -fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fno-common"
CXX="clang++"
CGO_ENABLED="1"

它并没有做很多事情。我正在访问 /,这将打开一个文件(ioutil.ReadAll),通过 text/template 运行它并输出结果。在该页面上没有进行任何 SQL 调用,但所有内容都已关闭。此时不记录任何数据/日志/信息/诊断信息。我已经删除了我能想到的一切。 - Mark
go version go1.2 darwin/amd64 - Mark
你尝试过使用 go prof 吗? - joshlf
我最近在我的Web应用程序上使用了一个:https://gist.github.com/ancarda/92d82f4da8f63012424d -- 不过我不太确定如何阅读它的输出。 - Mark
我不确定,但我猜这意味着“在此函数内分配了这么多内存”(可能使用newmake调用 - 这仅是堆使用,对吧?)。 - joshlf
显示剩余3条评论
1个回答

19

从你在评论中提供的 pprof 堆转储来看,似乎通过 gorilla/sessionsgorilla/context 泄漏了内存(近400MB)。

请参考这个邮件列表线程:https://groups.google.com/forum/#!msg/gorilla-web/clJfCzenuWY/N_Xj9-5Lk6wJ 以及这个 GitHub 问题:https://github.com/gorilla/sessions/issues/15

以下是一个极快地泄漏内存的版本:

package main

import (
    "fmt"
    // "github.com/gorilla/context"
    "github.com/gorilla/sessions"
    "net/http"
)

var (
    cookieStore = sessions.NewCookieStore([]byte("cookie-secret"))
)

func main() {
    http.HandleFunc("/", defaultHandler)
    http.ListenAndServe(":8080", nil)
}

func defaultHandler(w http.ResponseWriter, r *http.Request) {
    cookieStore.Get(r, "leak-test")
    fmt.Fprint(w, "Hi there")
}

这是一个可以清理并具有相对稳定RSS的例子:

package main

import (
    "fmt"
    "github.com/gorilla/context"
    "github.com/gorilla/sessions"
    "net/http"
)

var (
    cookieStore = sessions.NewCookieStore([]byte("cookie-secret"))
)

func main() {
    http.HandleFunc("/", defaultHandler)
    http.ListenAndServe(":8080", context.ClearHandler(http.DefaultServeMux))
}

func defaultHandler(w http.ResponseWriter, r *http.Request) {
    cookieStore.Get(r, "leak-test")
    fmt.Fprint(w, "Hi there")
}

是的,看起来这是CookieStore中的泄漏。当我将其剥离后,内存使用情况就很正常了。我不明白为什么需要context.ClearHandler。至于裸HTTP服务器上升的内存使用情况,似乎只是垃圾收集器的作用。 - Mark
5
gorilla/context 内部使用 map[request]... 存储数据(而 sessions使用 context),因此处理程序需要在请求终止后从 map 中删除请求。看起来 gorilla/sessions 被设计为与 gorilla/mux 路由器一起使用(它会自动清除该映射)。 - az_
谢谢,我已经查看了网站,没有提到这个。 - Mark

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接