记一次golang内存泄露

记一次golang内存泄露

最近在QA环境上验证功能时,发现机器特别卡,查看系统内存,发现可用(available)内存仅剩200多M,通过对进程耗用内存进行排序,发现有一个名为application-manager的容器服务的内存占用达到700多M,该服务使用Gin框架对外提供操作k8s资源的简单功能,解析客户端请求,并将结果返回给客户端。由于是测试环境,访问量极少,但内存一直只增不减,从最初的10M,一直增加到700多M,最终由于OOM而被重启(Pod)。

最初使用go pprof来尝试定位是否代码中存在如未释放的全局变量或存在goroutine泄露。初步定位后发现并没有goroutine泄露。/debug/pprof/allocs导出的svg文件参见这里

# go tool pprof http://127.0.0.1/debug/pprof/allocs
Fetching profile over HTTP from http://oms.qa.internal.hsmob.com/debug/pprof/allocs
Saved profile in C:\Users\liuch\pprof\pprof.application-manager.alloc_objects.alloc_space.inuse_objects.inuse_space.005.pb.gz
File: application-manager
Type: alloc_space
Time: May 24, 2021 at 9:21am (CST)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) web

内存占用图中唯一一个与自己的功能有关的代码就是setK8sEvent,至此可以初步怀疑问题所在。但问题点并不明确。另外在图中可以看到占用内存最多的函数是goLookupIpCNAMEOrder,该函数与域名查找有关,可能是建立链接前的步骤。

进入该容器(application-manager),查看底层链接情况,可以看到在该容器中建立了9492条TCP链接。至此已经找到问题根因,内存泄露的原因是没有及时关闭TCP,导致创建了大量socket,占用大量内存。

# ss -s
Total: 10215 (kernel 15097)
TCP:   9492 (estab 7953, closed 1537, orphaned 0, synrecv 0, timewait 1106/0), ports 0

Transport Total     IP        IPv6
*         15097     -         -
RAW       0         0         0
UDP       0         0         0
TCP       7955      7952      3
INET      7955      7952      3
FRAG      0         0         0

在容器中使用ss -ntp命令进一步查看是创建了到哪些服务链接。最终发现创建了大量到elasticsearch 9200端口的链接,结合前面提到的setK8sEvent函数(该函数会将k8s事件发送给elasticsearch),基本可以确定是在读取es的数据之后,忘了执行resp.Body.Close()操作。

后记

代码中的确存在没有执行resp.Body.Close()的操作,修复之后发现内存占用正常。

总结

  • 如果golang程序发现内存泄露,可以首先检查socket泄露。
posted @ 2021-05-24 10:47  charlieroro  阅读(1298)  评论(2编辑  收藏  举报