Golang内存优化实践指南
最近做了许多有关Go内存优化的工作,总结了一些定位、调优方面的套路和经验,于是,想通过这篇文章与大家分享讨论。 发现问题 性能优化领域有一条总所周知的铁律,即:不要过早地优化。编写一个程序,首先应该保证其功能的正确性,以及诸如设计是否合理、需求等是否满足,过早地优化只会引入不必要的复杂度以及设计不合理等各种问题。 那么何时才能开始优化呢?一句话,问题出现时。诸如程序出现频繁OOM,CPU使用率异常偏高等情况。如今,在这微服务盛行的时代,公司内部都会拥有一套或简单或复杂的监控系统,当系统给你发出相关告警时,你就要开始重视起来了。 问题定位 1. 查看内存曲线 首先,当程序发生OOM时,首先应该查看程序的内存使用量曲线,可以通过现有监控系统查看,或者prometheus之类的开源工具。 曲线一般都是呈上升趋势,比如goroutine泄露的曲线一般是使用量缓慢上升直至OOM,而内存分配不合理往往时在高负载时快速攀升以致OOM。 2. 问题复现 这块是可选项,但是最好能保证复现。如果能在本地或debug环境复现问题,这将非常有利于我们反复进行测试和验证。 3. 使用pprof定位 Go官方工具提供了pporf来专门用以性能问题定位,首先得在程序中开启pprof收集功能,这里假定问题程序已开启pprof。(对这块不够了解的同学,建议通过这两篇文章(1, 2)学习下pprof工具的基本用法) 接下来,我们复现问题场景,并及时获取heap和groutine的采样信息。 获取heap信息: curl http://loalhost:6060/debug/pprof/heap -o h1.out 获取groutine信息:curl http://loalhost:6060/debug/pprof/goroutine -o g1.out 这里你可能想问,这样就够了吗? 当然不是,只获取一份样本信息是不够的。内存使用量是不断变化的(通常是上升),因此我们需要的也是期间heap、gourtine信息的变化信息,而非瞬时值。一般来说,我们需要一份正常情况下的样本信息,一份或多份内存升高期间的样本信息。 数据收集完毕后,我们按照如下3个方面来排查定位。 排查goroutine泄露 使用命令go tool pprof --base g1.out g2.out ,比较goroutine信息来判断是否有goroutine激增的情况。 进入交互界面后,输入top命令,查看期间goroutine的变化。 同时可执行go tool pprof --base g2.out g3.out来验证。我之前写了的一篇实战文章,记录了goroutine泄露的排查过程。 排查内存使用量 使用命令go tool pprof --base h1.out h2.out,比较当前堆内存的使用量信息来判断内存使用量。 进入交互界面后,输入top命令,查看期间堆内存使用量的变化。 排查内存分配量 当上述排查方向都没发现问题时,那就要查看期间是否有大量的内存申请了,以至于GC都来不及回收。使用命令go tool pprof --alloc_space --base h1.out h2.out,通过比较前后内存分配量来判断是否有分配不合理的现象。 进入交互界面后,输入top命令,查看期间堆内存分配量的变化。 一般来说,通过上述3个方面的排查,我们基本就能定位出究竟是哪方面的问题导致内存激增了。我们可以通过web命令,更为直观地查看问题函数(方法)的完整调用链。 问题优化 定位到问题根因后,接下来就是优化阶段了。这个阶段需要对Go本身足够熟悉,还得对问题程序的业务逻辑有所了解。 我梳理了一些常见的优化手段,仅供参考。实际场景还是得实际分析。 goroutine泄露 这种问题还是比较好修复的,需要显式地保证goroutine能正确退出,而非以一些自以为的假设来保证。例如,通过传递context.Context对象来显式退出 go func(ctx context.Context) { for { select { case <-ctx....