背景

最近开发了一个推送项目,使用Golang作为开发语言。作为Golang的入门小白,本着拿来主义改造了别人的代码,按照自己的逻辑开发完,测试通过后就上线了。

上线后一天通过falcon发现内存一直在缓慢的增长,知道了有内存泄露后但是无从下手。单机内存从4G增加到8G后并没有缓解问题,因为还是有OOM的隐患。

为了彻底查清楚OOM的原因,在网络上看了很多文档,很多都是推荐golang pprof工具。其中《实战Go内存泄露》写的很好,非常详细。

10次内存泄露,有9次是goroutine泄露。

https://studygolang.com/articles/20519

我也怀疑我的程序goroutine协程使用的有问题,不过没有石锤只能是猜测。

go pprof

go func() {

// terminal: $ go tool pprof -http=:8081 http://localhost:6060/debug/pprof/heap
// web:
// 1、http://localhost:8081/ui
// 2、http://localhost:6060/debug/charts
// 3、http://localhost:6060/debug/pprof
fmt.Println(http.ListenAndServe("0.0.0.0:6060", nil))
}()

直接上代码,在主程序main函数中加入这段代码。启动程序后可以通过下面的地址查看进程和协程的堆栈情况,http://localhost:6060/debug/pprof/。

每隔一段时间后会发现heap和goroutine都在增长。尤其是有推送任务的情况下涨的比较多,并且长时间不下降。

http://localhost:6060/debug/charts/ 这个地址可以通过曲线图描绘出内存的走势。

此外,还可以设置环境变量export GODEBUG=gctrace=1, 可以在程序运行时输出gc回收内存的过程。

查看了goroutine的分布,发现我的推送协程并不会增加,而是sql相关的协程一直在增加,由此我联想到每次协程操作mysql后都没有释放sql链接。排查了一遍代码,把相关错误处理和函数结束的地方都加上了close操作,在跑起来发现推送过后内存会迅速下降。而且,高峰期mysql链接也没有超高,推送速度也没有明显变慢,所以结果可以接受。

更好的方案可能是通过连接池的方式管理mysql链接。这可以作为一个优化点来实现。

总结

这次问题虽然解决了,但是并没有深刻理解goroutine的原理,也没有找到最好的解决办法,继续学习吧。

欢迎关注公众号『野狐』