专栏首页网管叨bi叨生产环境Go程序内存泄露,用pprof如何快速定位

生产环境Go程序内存泄露,用pprof如何快速定位

内存泄漏可以在整个系统中以多种形式出现,除了在写代码上的疏忽,忘了关闭该关闭的资源外,更多的时候导致系统发生内存泄露原因可能是设计上决策不对、或者业务逻辑上的疏忽没有考虑到一些边界条件。

比如查数据库时,有个查询条件在一定情况下应用不到,导致程序被迫持有一个超大的结果集,这样持续一段时间,执行相同任务的线程一多,就会造成内存泄露。

Golang 为我们提供了 pprof 工具。掌握之后,可以帮助排查程序的内存泄露问题,当然除了排查内存,它也能排查 CPU 占用过高,线程死锁的这些问题,不过这篇文章我们会聚焦在怎么用 pprof 排查程序的内存泄露问题。

Go 开发的系统中,怎么 添加 pprof 进行采样的步骤,在这里我就不再细说了,因为我之前的文章,对 pprof 的安装和使用做了详细的说明

当然如果你想尝试点更智能的,让程序能自己监控自己,并在出现抖动的时候自己采样,Dump 出导致内存、CPU的问题调用栈信息

内存泄露该看哪个指标

pprof工具集,提供了Go程序内部多种性能指标的采样能力,我们常会用到的性能采样指标有这些:

  • profile:CPU采样
  • heap:堆中活跃对象的内存分配情况的采样
  • goroutine:当前所有goroutine的堆栈信息
  • allocs: 会采样自程序启动所有对象的内存分配信息(包括已经被GC回收的内存)
  • threadcreate:采样导致创建新系统线程的堆栈信息

上面 heap 和 allocs 是两个与内存相关的指标, allocs 指标会采样自程序启动所有对象的内存分配信息。一般是在想要分析哪些代码能优化提高效率时,查看的指标。针对查看内存泄露问题的分析,使用则的是 heap 指标里的采样信息。

Heap

pprof 的 heap 信息,是对堆中活跃对象的内存分配情况的采样。Go 里边哪些对象会被分配到堆上?一般概况就是,被多个函数引用的对象、全局变量、超过一定体积(32KB)的对象都会被分配到堆上,当然对于 Go 来说还会有其他的一些情况会让对象逃逸到堆上。

具体哪些变量会被分配到堆上、以及内存逃逸的事儿,就不多说

Heap 采样

要使用 pprof 获取 heap 指标的采样信息,一种情况是使用 "net/http/pprof" 包

import (
 "net/http/pprof"
)

func main() {
 http.HandleFunc("/debug/pprof/heap", pprof.Index)
 ......
 http.ListenAndServe(":80", nil)
}

然后通过 HTTP 请求的方式获得

curl -sK -v https://example.com/debug/pprof/profile > heap.out

还有一种主要的方法是使用runtime.pprof 提供的方法,把采样信息保存到文件。

pprof.Lookup("heap").WriteTo(profile_file, 0)

关于这两个包的使用方式,以及怎么把信息采样到文件,上面介绍自动采样的文章里有详细的介绍,这里就不再花过多篇幅了。

下面进入文章的正题, 拿到采样文件后,怎么用 pprof 排查出代码哪里导致了内存泄露。

用 pprof 找出内存泄露的地方

pprof 在采样 heap 指标的信息时,使用的是 runtime.MemProfile 函数,该函数默认收集每个 512KB 已分配字节的分配信息。我们可以设置让 runtime.MemProfile 收集所有对象的信息,不过这会对程序的性能造成影响。

当我们拿到采样文件后,就可以通过 go tool pprof 将信息加载到一个交互模式的控制台中。

> go tool pprof heap.out

进入,交互式控制台后,一般会有如下的提示:

File: heap.out
Type: inuse_space
Time: Feb 1, 2022 at 10:11am (CST)
Entering interactive mode (type "help" for commands, "o" for options)

这里的 Type: inuse_space 指明了文件内采样信息的类型, Type 可能的值有:

  • inuse_space — 已分配但尚未释放的内存空间
  • inuse_objects——已分配但尚未释放的对象数量
  • alloc_space — 分配的内存总量(已释放的也会统计)
  • alloc_objects — 分配的对象总数(无论是否释放)

接下来,介绍一个 pprof 交互式模式下的命令 top,也可以是 topN,比如 top10。这个跟Linux 系统的 top 命令类似,输出 Top N 个最占用内存的函数。

(pprof) top10
Showing nodes accounting for 134.55MB, 92.16% of 145.99MB total
Dropped 60 nodes (cum <= 0.73MB)
Showing top 10 nodes out of 117
      flat  flat%   sum%        cum   cum%
   60.53MB 41.46% 41.46%    85.68MB 58.69%  github.com/jinzhu/gorm.glob..func2
   18.65MB 12.77% 54.24%    18.65MB 12.77%  regexp.(*Regexp).Split
   16.95MB 11.61% 65.84%    16.95MB 11.61%  github.com/jinzhu/gorm.(*Scope).AddToVars
    8.67MB  5.94% 71.78%   129.05MB 88.39%  example.com/xxservice/dummy.GetLargeData
    7.50MB  5.14% 82.63%     7.50MB  5.14%  reflect.packEface
    6.50MB  4.45% 87.08%     6.50MB  4.45%  fmt.Sprintf
       4MB  2.74% 89.82%        4MB  2.74%  runtime.malg
    1.91MB  1.31% 91.13%     1.91MB  1.31%  strings.Replace
    1.51MB  1.03% 92.16%     1.51MB  1.03%  bytes.makeSlice

在这两个里边,最占用内存的前三是 gorm 库的一个方法,gorm 是个 ORM 库,但是导致它内存泄露的原因应该是后面一个有业务逻辑的代码,dummy.GetLargeData 方法。

在 top 指令输出的列表中,我们可以看到两个值,flat 和 cum。

  • flat:表示此函数分配、并由该函数持有的内存空间。
  • cum:表示由这个函数或它调用堆栈下面的函数分配的内存总量。

此外 sum % 表示前面几行输出的 flat百分比之和, 比如上面第四行 sum% 列的值是, 71.78% 实际上就是它以及它上面三行输出的 flat% 的总和。

定位到导致内存泄露的函数后,后面要做的优化问题就是,深入函数内部,看哪里使用不当或者有逻辑上的疏忽,比如我开头举得那个查询条件在有些情况下应用不上的例子。

当然如果你想在函数内部再精确的定位到底是哪段代码导致的内存溢出,也是有办法的,这时候就需要用到 list 指令了。

list 指令可以列出函数内部,每一行代码运行时分配的内存(如果分析CPU的采样文件,则会显示CPU使用时间)

(pprof) list dummy.GetLargeData
Total: 814.62MB
ROUTINE ======================== dummy.GetLargeData in /home/xxx/xxx/xxx.go
  814.62MB   814.62MB (flat, cum)   100% of Total
         .          .     20:    }()
         .          .     21:
         .          .     22:    tick := time.Tick(time.Second / 100)
         .          .     23:    var buf []byte
         .          .     24:    for range tick {
  814.62MB   814.62MB     25:        buf = append(buf, make([]byte, 1024*1024)...)
         .          .     26:    }
         .          .     27:}
         .          .     28:

总结

这里把用 pprof 怎么排查程序的内存泄露做了个简单的总结,当然如果你们公司有条件上持续采样,或者我之前文章说的自动采样方案的话,最好还是用上,让机器帮我们做这些事情。

不过不管是用什么办法,最终只能是帮我们定位出来哪里造成了内存泄露,至于要怎么优化解决这个问题,还得具体情况具体分析,如果是一些业务逻辑实现上的问题,那就得跟团队商量一下实现方式,可能还会涉及到产品上的一些改动。

- END -

文章分享自微信公众号:
网管叨bi叨

本文参与 腾讯云自媒体分享计划 ,欢迎热爱写作的你一起参与!

作者:KevinYan11
原始发表时间:2022-02-07
如有侵权,请联系 cloudcommunity@tencent.com 删除。
登录 后参与评论
0 条评论

相关文章

  • 实战Go内存泄露

    最近解决了我们项目中的一个内存泄露问题,事实再次证明pprof是一个好工具,但掌握好工具的正确用法,才能发挥好工具的威力,不然就算你手里有屠龙刀,也成不了天下第...

    大彬
  • 【实践】golang pprof 实战-CPU,heap,alloc,goroutine,mutex,block

    如果要说在 golang 开发过程进行性能调优,pprof 一定是一个大杀器般的工具。但在网上找到的教程都偏向简略,难寻真的能应用于实战的教程。这也无可厚非,毕...

    辉哥
  • 快速定位线上性能问题:Profiling 在微服务应用下的落地实践

    目前, Freewheel 核心任务系统采用微服务架构,在降低服务间耦合的同时,也对每个服务的鲁棒性提出了更高的要求。每个模块作为独立服务部署,都可能面临诸如性...

    深度学习与Python
  • golang定位内存泄露与cpu占用过高的方法与实战

    现在使用golang的项目越来越多,但是当golang发生内存泄露或cpu占用过高时,怎么定位呢?其实很简单,按如下所述步骤操作即可:

    shionyu
  • go pprof实战

    1 为什么要进行性能优化 1.1 哪些情况需要进行性能优化 其实关于性能优化的主题,网上已经讨论很多次,这里谈一下我的理解,那么其实核心就是2个点: 服务一直...

    QQ音乐技术团队
  • 深度解密Go语言之pprof

    相信很多人都听过“雷神 3”关于性能优化的故事。在一个 3D 游戏引擎的源码里,John Carmack 将 1/sqrt(x) 这个函数的执行效率优化到了极致...

    梦醒人间
  • 使用 pprof 对 Go 程序进行分析优化 2022-05-01

    在生产环境中,偶尔会发生 Go 程序 CPU 暴增的现象,排除某时段并发大的场景外,通过监控面板看不到程序是因为什么原因导致的,Go 语言原生就提供了工具 pp...

    Meng小羽
  • 记一次golang内存泄露

    最近在QA环境上验证功能时,发现机器特别卡,查看系统内存,发现可用(available)内存仅剩200多M,通过对进程耗用内存进行排序,发现有一个名为appli...

    charlieroro
  • golang 内存分析/内存泄漏

    进入交互式模式之后,比较常用的有 top、list、traces、web 等命令。

    用户5705150
  • 新部署的服务 go_cpu 占满如何处理?

    作者:周易建,腾讯云云监控高级工程师 排查结果展示 [点击查看大图] 故障现象 新部署的服务,没有任何请求。但 Pod 上的 CPU 一直是占满状态,但...

    腾讯云监控团队
  • 让你最快上手 go 的 pprof 性能分析大杀器

    前言,发现一直没有记录过 pprof 分析的博客,其实在实际的业务场景中已经使用它很多次了,对于性能分析来说它真的是一大杀器,基本上有了它,80% 的性能问题都...

    LinkinStar
  • 利用火焰图对 Go 程序进行性能分析

    学会对应用系统进行运行时数据采集与性能分析是软件工程实践常用的基本技能。通常使用 profile 表示性能分析与采集,或者使用 profiling 代表性能分析...

    aoho求索
  • day3 | 高质量编程与性能调优实战 | 第三届字节跳动青训营笔记

    实际应用场景千变万化,各种语言的特性和语法各不相同。但是高质量编程遵循的原则是相通的

    千羽
  • Go 笔记之如何防止 goroutine 泄露(二)

    上篇文章说到,防止 goroutine 泄露可从两个角度出发,分别是代码层面的预防与运行层面的监控检测。今天,我们来谈第二点。

    波罗学
  • 内存泄漏的定位与排查:Heap Profiling 原理解析

    系统长时间运行之后,可用内存越来越少,甚至导致了某些服务失败,这就是典型的内存泄漏问题。这类问题通常难以预测,也很难通过静态代码梳理的方式定位。Heap Pro...

    PingCAP
  • Golang程序性能分析(一)pprof和go-torch

    最近计划用三篇文章讲述一下Golang应用性能分析,本文是第一篇,先来介绍Go语言自带的性能分析库pprof怎么使用,后面两篇会讲解怎么用pprof对Echo或...

    KevinYan
  • 无人值守的自动 dump(一)

    Go 项目做的比较大(主要说代码多,参与人多)之后,可能会遇到类似下面这样的问题:

    梦醒人间
  • Golang使用pprof监控性能及GC调优

    作者:峰云就她了 链接:http://xiaorui.cc/?p=3000 來源:个人博客

    李海彬
  • ​2021-03-12:go中,如何确定有没有内存泄露,系统里怎么去监控整体?

    2021-03-12:go中,如何确定有没有内存泄露,系统里怎么去监控整体的运行情况,日志是怎么处理的?

    福大大架构师每日一题

扫码关注腾讯云开发者

领取腾讯云代金券