后台填坑记——Golang内存泄漏问题排查(一)
(太长不看版)
DeepSeek生成的文章摘要
文章摘要
本文记录了Golang服务内存泄漏问题的排查过程,通过多维度工具分析和代码溯源,最终定位到Redis/SQL连接池未正确关闭导致的协程泄漏问题。文章系统性地展示了从现象分析到根因定位的全流程方法,对后台服务稳定性维护具有实践指导意义。
文章重点总结
问题现象:
服务内存缓慢增长,初步堆内存分析(heap pprof)未发现明显异常
Goroutine分析显示大量runtime.gopark等待状态协程,暗示协程泄漏可能
排查过程:
**双时间切片对比:**通过-base参数对比不同时段pprof文件,放大增量变化
Goroutine专项分析:
1.发现(*ConnPool).reaper相关泄漏线索
2.结合CPU分析无果后转向源码审查
关键代码溯源:
SQL库OpenDB方法创建connectionOpener协程未关闭
Redis连接池NewConnPool启动reaper协程未终止
技术原理:
Golang内存泄漏多源于协程泄漏(未进入GC回收的活跃对象)
连接池实现机制:
1.生产者协程监听连接创建请求(channel)
2.需显式调用Close()释放资源
解决方案:
1.确保正确调用client.Close()关闭Redis连接
2.规范SQL连接池的生命周期管理
工具使用亮点
1.多维度pprof组合分析(heap/goroutine/cpu)
2.Peek视图解析调用关系占比
3.差异化对比(-base)技术定位增量问题
4.源码级逆向工程验证假设
评价
本文真实还原了线上问题排查的全场景,展现了"工具分析+逻辑推理+源码验证"的完整技术闭环。文章包含大量可复现的操作命令和代码分析片段,为Golang服务稳定性维护提供了标准的排查范本。其"从现象到本质"的推导过程对培养工程思维尤具参考价值。
|
|
|
|
|
|
(长文版)
原文如下
近期的项目中,发现了一个奇怪的现象,某一个服务的内存似乎在缓慢的泄漏,内存监控指标如下:
golang的服务,线上看内存问题标准组件——pprof搞起
(一)抓一个内存切片
首先看了下heap,在线上服务执行:
curl -o heap.pprof "http://localhost:6668/debug/pprof/heap?debug=0"
获取到了heap.pprof文件,然后在本地网页打开:
go tool pprof -seconds=10 -http=:9998 Desktop/heap.pprof
因为是疑似内存泄漏,所以SAMPLE选择:inuse_space
结果如下:
猛的一看貌似没啥重要的信息,最多的内存都是正常的业务逻辑需要,这里一般来说很难直接看出来原因,内存泄漏的不多,所以很可能在insue_space里面占了很小的一块
(二)抓两个内存切片对比
既然占比很小,那就按照“时间换空间”的思路放大问题,隔一个合适的时间间隔分别抓一个内存切片,然后用diff的方式查看:
过了几天(这里泄漏比较慢),再抓一个pprof
curl -o heap2.pprof "http://localhost:6668/debug/pprof/heap?debug=0"
然后在本地命令行执行:
go tool pprof -base -http=:9997 Desktop/heap.pprof Desktop/heap2.pprof
如果所示:
可以看到绿色的为相对之前的内存切片减少的,而红色是相比之前的内存切片增加的,可以看到,shtrings.genSplit和NewCommonRedisUnit这里有少量的内存,加在一起才1Mb左右,而整体的内存增长超过了3Mb,看起来这里并没有找到原因!
(三)抓一个Goroutine看看
既然内存增长在堆中不明显,那可能内存泄漏出现在栈中!抓个Goroutine看看
Golang的内存泄漏绝大部分出现在Goroutine泄露上,也就是协程泄漏,这里的泄漏并不会在Heap中有体现
抓下协程情况
curl -o goroutine.pprof "http://localhost:6668/debug/pprof/goroutine?debug=0"
然后在本地命令行执行:
go tool pprof -http=:9996 Desktop/goroutine.pprof
结果如下:
好消息是,类别倒是少且清晰;
坏消息是,都是系统函数!
不过,这里有一个很重要的提示点:runtime.gopark
这个函数,其实如果解过几个泄漏的问题的话,就会主导,几乎在所有的 goroutine 泄露中都会看到有,并且都会是大头,既然是大头,那这个函数是啥作用:看下源码:
func g