当前位置: 首页 > 科技观察

生产环境中Go程序内存泄漏,如何使用Pprof快速定位

时间:2023-03-19 13:27:43 科技观察

内存泄漏在整个系统中可以以各种形式出现,除了写代码时的疏忽,忘记关闭已关闭的资源,更多时候是导致的原因对于系统中的内存泄漏,可能是设计决策错误,也可能是业务逻辑上的疏忽,没有考虑到一些边界条件。例如,在检查数据库时,在某些情况下无法应用查询条件,导致程序被迫持有很大的结果集。如果这样持续一段时间,如果有更多的线程在执行同一个任务,就会发生内存泄漏。Golang为我们提供了pprof工具。掌握后,可以帮助排查程序的内存泄漏问题。当然除了排查内存外,还可以排查CPU占用率高、线程死锁等问题。但是,在本文中,我们将重点介绍如何使用pprof来排查程序的内存泄漏问题。.在Go开发的系统中,如何添加pprof进行采样步骤,这里不再赘述,因为我之前的文章对pprof的安装和使用有详细的介绍,文章链接放在这里:GolangProgramperformance分析(1)pprof和火焰图分析Golang程序性能分析(2)在Echo和Gin框架中使用pprofGolang程序性能分析(3)使用pprof分析gRPC服务的性能当然,如果你想尝试更聪明的东西,let程序可以在抖动发生时进行自我监控和采样,并转储导致内存和CPU问题的调用堆栈信息。你可以看看我在下面两篇文章中介绍的方法和实用类库。学习这些技巧,让Go程序可以监控自己的Go服务,进行自动采样性能分析。内存泄漏的方案设计与实现看哪个指标pprof工具集提供了Go程序内部各种性能指标的采样能力,我们经常用到性能采样指标包括这些:profile:CPU采样heap:对内存分配的采样heapgoroutine中的活跃对象:当前所有goroutine的栈信息allocs:会采样程序启动以来所有对象的内存分配信息(包括已经被GC回收的内存)threadcreate:采样导致的栈信息创建新的系统线程。上面的heap和allocs是内存相关的两个指标。allocs指标会采样程序启动以来所有对象的内存分配信息。一般是一个指标,当你要分析哪些代码可以优化,提高效率的时候检查。对于查看内存泄漏的分析,使用了堆指示器中的采样信息。Heappprof的堆信息是堆中活动对象的内存分配的一个采样。Go中哪些对象会分配到堆上?总的来说就是被多个函数引用的对象,全局变量,超过一定大小(32KB)的对象都会分配在堆上。当然对于Go来说,还会有其他一些情况会导致对象逃逸到堆中。哪些变量分配到堆和内存逃逸就不多说了。如果想看详细内容,请阅读以下两篇文章。Go内存管理器的内存分配策略的图形说明。Go内存管理代码的逃逸分析。")funcmain(){http.HandleFunc("/debug/pprof/heap",pprof.Index)......http.ListenAndServe(":80",nil)}通过HTTP请求获取curl-sK-vhttps://example.com/debug/pprof/profile>heap.out另一个主要的方法是使用runtime.pprof提供的方法将采样信息保存到一个文件中,pprof.Lookup("heap")。WriteTo(profile_file,0)关于这两个包的使用方法以及如何将采样信息存入文件,上面关于自动采样的文章有详细的介绍,这里就不多说了,太长了,进入正题文章的最后,如何使用pprof找出代码在获取示例文件后导致内存泄漏的位置。默认每512KB分配字节的分配信息,我们可以设置runtime.MemProfile来收集所有对象的信息,但是这样会影响性能程序的nce。当我们得到示例文件后,我们可以通过gotoolpprof将信息加载到交互模式的控制台中。>gotoolpprofheap.out进入,在交互控制台后,一般会有如下提示:File:heap.outType:inuse_spaceTime:Feb1,2022at10:11am(CST)Enteringinteractivemode(type"help"forcommands,"o"foroptions)这里输入:inuse_space表示文件中采样信息的类型,Type可能的取值有:inuse_space——已经分配但还没有释放的内存空间inuse_objects——数量ofobjectsthathavebeenallocatedbutetyetyetreleasedalloc_space—已分配内存总量(已释放也会被统计)alloc_objects—已分配对象总数(无论是否已释放)接下来介绍一个pprof交互方式命令top,也可以是topN,比如top10。这类似于Linux系统的top命令,输出TopN最耗内存的函数。(pprof)top10Showingnodesaccountingfor134.55MB,92.16%of145.99MBtotalDropped60nodes(cum<=0.73MB)Showingtop10nodesoutof117flatflat%sum%cumcum%60.53MB41.46%41.466%85github.com网站/jinzhu/gorm.glob..func218.65MB12.77%54.24%18.65MB12.77%正则表达式。(*正则表达式)。拆分16.95MB11.61%65.84%16.95MB11.61%github.com/jinzhu/gorm。(*范围)。AddToVars8.67MB5.94%71.78%129.05MB88.39%example.com/xxservice/dummy.GetLargeData7.50MB5.14%82.63%7.50MB5.14%reflect.packEface6.50MB4.45%87.08%4.45%printf44%运行时44.72%89.72%8.malg1.91MB1.31%91.13%1.91MB1.31%strings.Replace1.51MB1.03%92.16%1.51MB1.03%bytes.makeSlice这两个里面,gorm占用内存最多的一个库的方法,gorm是一个ORM库,但其内存泄漏的原因应该是以下带有业务逻辑的代码,dummy.GetLargeData方法。在top命令输出的列表中,我们可以看到两个值,flat和cum。flat:表示本函数分配的内存空间,由本函数持有。cum:表示这个函数或者它的调用栈下面的函数分配的内存总量。此外,sum%表示前面几行输出的固定百分比的总和。比如上面第四行sum%那一列的值,71.78%其实就是它和上面三行输出的flat%之和。定位到导致内存泄漏的函数之后,后面要做的优化问题就是深入函数,看它哪里使用不当或者有逻辑遗漏,比如我开头提到的例子,查询条件不能在某些情况下得到应用。当然,如果你想准确定位到函数内部是哪一段代码导致了内存溢出,也是有办法的。这时候就需要用到list命令了。list命令可以列出函数内部每行代码运行时分配的内存(如果分析CPU采样文件,会显示CPU使用时间)(pprof)listdummy.GetLargeDataTotal:814.62MBROUTINE========================dummy.GetLargeDatain/home/xxx/xxx/xxx.go814.62MB814.62MB(持平,累积)总计的100%。.20:}()。.21:。.22:tick:=time.Tick(time.Second/100)。.23:变量buf[]字节。.24:对于范围刻度{814.62MB814.62MB25:buf=append(buf,make([]byte,1024*1024)...)。.26:}..27:}。.28:小结这里简单总结一下如何使用pprof排查程序内存泄露问题。当然,如果你们公司在条件上有Continuoussampling,或者我在上一篇文章中提到的自动采样方案,最好使用它,让机器为我们做这些事情。但是,不管用什么方法,最终都只能帮助我们定位到内存泄漏的原因所在。至于如何优化和解决这个问题,需要具体情况具体分析。如果是一些业务逻辑在实现上有问题,那我们就得跟团队商量一下实现方式,这也可能涉及到产品的一些变化。