开发了个cas服务,实际使用上发现内存蹭蹭蹭的网上张,最后甚至超过了1G。于是引入pprof进行调查。
由于服务使用gin框架,这里有个插件github.com/gin-contrib/pprof
,代码里如下使用:
r := gin.Default()
if gin.Mode() == gin.DebugMode {
pprof.Register(r, "/cas/debug")
}
启动服务,浏览器中打开http://localhost:8080/cas/debug/,能看见pprof信息了。
接下来通过Insomnia请求,发现goroutine每次请求+3个且不会释放。ok,先确定问题在goroutine这里了。
对代码进行肉眼检查,使用到goroutine的代码就一处,注释掉goroutine还是只加不减,这里看来问题不在这里。反复请求几次后,下载goroutine,heap和trace,运行go tool pprof XXX
检查,没有代码相关的的名字,算是毫无头绪。于是用最笨的方法进行排查,一片一片的注释,测试,还好go服务启动快。
没用多久,就锁定到了一处http请求这里。我发现一调用ioutil.ReadAll,goroutine就上去了。于是乎,换其他的写法,io.Copy甚至response.Body.Read,统统无效。想想看ioutil.ReadAll是官方例子标准写法,不至于出内存泄露问题,那么问题就在其他不标准的写法。
仔细查这个http请求,由于内网环境ssl证书问题,这里没有使用DefaultClient,使用了
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
于是尝试着使用http端口,去掉Transport,使用回DefaultClient,问题解决。后来换上有效的ssl证书,也没有问题了。
官方文档介绍:
Clients and Transports are safe for concurrent use by multiple goroutines and for efficiency should only be created once and re-used.
估计是ssl证书无效,另一方面,由于自定义http client的时候,省略了太多的timeout参数,导致goroutine无法释放。
自定义Transport时需要注意内存问题。
https://github.com/golang/go/blob/master/src/net/http/transport.go