Skip to main content

内存泄露

排查Go内存泄漏-样本下载服务

内存泄漏常发生于程序BUG,比如http.Body等资源没有正确关闭等。

我曾经临时接手过一个样本下载服务,这个下载服务线上会不定期OOM,在开发测试环境不好复现,因为下载量不够。 又因为服务部署在K8S上面,OOM后服务是会自动重启的,消息也有ACK确保不会丢失,因此这个问题当时并没有很突出。

我接手后,才开始正式去做梳理。 我当时的怀疑点就是某些资源没有关闭导致的,但因为是陌生项目,没有很好的切入口。因此排查方式就是通过pprof工具。

pprof通常有两种使用方式:

  • 注册 pprof 路由服务,通过浏览器直接查看。能看到基础信息,但可读性不高。
  • 通过可视化工具 graphviz(哥rua肥死),生成火焰图、可视化调用链路图
命名行查看
>>> go tool pprof head.pprof
>>> top // 查看top
>>> list // 查看源码
>>> web // 生成链路图
在线查看
>>> go tool pprof -http localhost:5000 head.pprof // 静态统计结果
>>> go tool pprof -http localhost:5000 http://localhost:8080/debug/pprof/profile // 实时

其中火焰图结构:

  • 横轴:表示时间,从左到右表示程序的执行时间
  • 纵轴:表示函数调用栈深度,从上到下表示函数调用关系
  • 矩形块:表示函数调用的持续时间

颜色深浅通常用于表示调用深度,颜色较深的部分表示调用栈较深,颜色较浅的部分表示调用栈较浅。深色通常表示耗时较多的函数。

定位问题我们可以通过两个点:

  • 宽度:表示该函数在整个时间范围内占用了较多时间
  • 深度:表示在该调用栈上花费了较多时间

通过定位,发现一个函数HttpDownload函数,没有关闭http.Body。修复就是简简单单加一行代码就修复了问题。


func main() {
go func() {
http.ListenAndServe("localhost:6060", nil)
}()

go Downloader()

//创建文件用于保存 pprof 数据
file, err := os.Create("heap.pprof")
if err != nil {
return
}
defer file.Close()

// 等待时间收集堆内存分配数据
time.Sleep(time.Second * 60)
pprof.WriteHeapProfile(file)
fmt.Println("exit")
return
}