记一次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:Usersliuchpprofpprof.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泄露。