记一次golang的内存泄露

摘要:
2) 在服务器上安装golangpprof程序以进行数据采集=nil{22log.Println23}24stmt.Close()25connect。Close()2627//******************clearemforgc**********28stmt=nil29tx=nil30connect=nil3/////////\/////////////////////5)发布修改后的代码,观察系统内存是否可以正常恢复和释放。真正的原因是gc内存释放不够及时,存在滞后现象。因此,最好的做法是在golang中使用大型对象或频繁创建内存时,将对象设置为obj=nil,并告诉gc我真的不再使用这个内存块,这样gc可以快速回收并减少迭代gc。

程序功能

此程序的主要功能是将文件中数据导入到clickhouse数据库中。

【问题描述】

服务器内存每隔一段时间会耗尽
记一次golang的内存泄露第1张

【问题分析】

由于使用的是go语言开发的,所以采用了业界流行的工具pprof。

参考URL:https://cizixs.com/2017/09/11/profiling-golang-program/

工具的使用与思路:
1)先修改源代码
2)安装工具观察
3)根据工具抓取的现象进行分析
4)修复内存缺陷代码, 再根据分析结果修复内存泄漏的地方
5)发布代码进行再跟踪分析

1)修改代码:
使用这个工具前需要在代码中写几行代码,以便能使用这个工具的来收集数据。
记一次golang的内存泄露第2张
记一次golang的内存泄露第3张

1 //引用pprof
2 import "net/http"
3 import_ "net/http/pprof"
4
5 //在主函数中新增端口监控程序
6 //由于我的代码本来就是守护进程,所以这里采用新开一个监听协程方式,防止阻塞
7 func main(){
8 go func(){
9 http.ListenAndServe("0.0.0.0:80", nil)
10 }()
11 //其他代码
12 ...
13 }

经过上面的源代码改造后,重新部署到服务器上,观察内存状况;
内存仍然可以重新持续消耗内存不释放的现象。

2)在服务器上安装 golang pprof 程序,进行数据采集。

安装方法:yum install golang pprof

3)使用命令对heap进行dump分析,这个工具的好处是dump后可以直接生成pdf或png

1 [root@centos ~]# go tool pprof /root/clickhouse_runner/clickhouse_mssql_e
tl http://0.0.0.0:80/debug/pprof/heap
2 Fetching profile over HTTP from http://0.0.0.0:80/debug/pprof/heap
3 Saved profile in /root/pprof/pprof.clickhouse_mssql_etl.alloc_objects.all
oc_space.inuse_objects.inuse_space.012.pb.gz
4 File: clickhouse_mssql_etl
5 Type: inuse_space
6 Time: Feb 5, 2020 at 4:15pm (CST)
7 Entering interactive mode (type "help" for commands, "o" for options)
8 (pprof) pdf
9 Generating report in profile003.pdf
10 (pprof) quit
11 [root@centos ~]

记一次golang的内存泄露第4张
记一次golang的内存泄露第5张
通过上面的heap 来分析,可以很明显的看到代码中主要的内存使用地方在于clickhouse 的驱动中,调用clickhouse的部分在创建内存没有释放(后来仔细分析了下golang的内存gc逻辑是由于gc速度存在滞后现象,而导入程序创建速度又很快,所以才导致gc越来越慢)。
记一次golang的内存泄露第6张

4)找到内存泄漏的源头,开始修改代码
修改前源代码:

1 connect, err := sql.Open("clickhouse", connstring)
2 if err != nil {
3 return err
4 }
5 load_start := time.Now()
6 tx, err := connect.Begin()
7 if err != nil {
8 log.Println(full_filename, "begin err", err)
9 return err
10 }
11 stmt, err := tx.Prepare("insert ... values....")
12 if err != nil {
13 log.Println(full_filename, "preare err", err)
14 return err
15 }
16 _, er := stmt.Exec(...)
17 if er != nil {
18 log.Println("err", er)
19 }
20 er2 := tx.Commit()
21 if er2 != nil {
22 log.Println(db_view, "err", er2)
23 }
24 stmt.Close()
25 connect.Close()

//通过自己写的代码与clickhouse 驱动代码的分析,总结可以有两种方式来改进内存泄
漏:
a.修改clickhouse中的驱动代码,再执行完代码后立即进行重置内存,而不等gc来处理:

1 func (stmt *stmt) Close() error {
2 stmt.ch.logf("[stmt] close")
3 //新增再次回收内存数据
4 if stmt.ch.block != nil {
5 stmt.ch.block.Reset()
6 }
7 return nil
8 }

b. 直接释放stmt的对象,利用gc 的自动回收(考虑后还是采用这个方式更合理些)

1 stmt.Close()
2 connect.Close()
3 //新增直接将stmt,connect对象置nil
4 //clear mem
5 stmt = nil
6 tx = nil
7 connect = nil

修改后完整的代码:

1 connect, err := sql.Open("clickhouse", connstring)
2 if err != nil {
3 return err
4 }
5 load_start := time.Now()
6 tx, err := connect.Begin()
7 if err != nil {
8 log.Println(full_filename, "begin err", err)
9 return err
10 }
11 stmt, err := tx.Prepare("insert ... values....")
12 if err != nil {
13 log.Println(full_filename, "preare err", err)
14 return err
15 }
16 _, er := stmt.Exec(...)
17 if er != nil {
18 log.Println("err", er)
19 }
20 er2 := tx.Commit()
21 if er2 != nil {
22 log.Println(db_view, "err", er2)
23 }
24 stmt.Close()
25 connect.Close()
26
27 //***** clear mem for gc ******
28 stmt = nil
29 tx = nil
30 connect = nil
31 //////////////////////////////////////////////////////////////////////////////////

5) 发布修改后的代码,进行观察,通过观察发现系统内存可以正常回收与释放
记一次golang的内存泄露第7张

【结论】

经过本次golang的调试发生,真正的原因是gc内存释放不够及时,存在滞后性(通过其他服务器观察发现,当压力小的时候,内存是可以正常释放的)。
所以最佳实践还是,在涉及到golang中使用大对象或者频繁创建内存的时候,要采用将对象设置能obj = nil 的方式,告知gc 我已经确实不再使用该内存块了,以便gc快速的回收,减少迭代gc。
另外,这种方式是可以应用到如java,c# 等语言身上的,它们都存在类似的问题。

免责声明:文章转载自《记一次golang的内存泄露》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇QtCreator中常用快捷键总结 狼人:《Skyline 监控系统工作原理分析》下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Golang操作Redis

redis 简介 redis(REmote DIctionary Server)是一个由Salvatore Sanfilippo写key-value存储系统,它由C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value类型的数据库,并提供多种语言的API。和Memcached类似,它支持存储的value类型相对更多,包括str...

CentOS7 编译安装golang和rpm安装golang

编译安装 1、下载golang二进制安装包: https://storage.googleapis.com/golang/go1.9.linux-amd64.tar.gz 2、解压安装包到指定目录,此处以解压到/usr/local目录下为例: 1 tar -C /usr/local -xzf ../packages/go1.8.linux-amd64.ta...

C/C++调用Golang 一

C/C++调用Golang 一 (开发环境: 操作系统: windows 7 32位操作系统 C++: visual studio 2010 Golang:go version go1.9 windows/386 TDM-GCC-32) 用一个简单的例子演示如何在C++中调用golang程序。用golang编写一个简单的函数,编译成动态链接...

golang中使用Redis

一.golang中安装Redis github地址:https://github.com/garyburd/redigo 文档地址:http://godoc.org/github.com/garyburd/redigo/redis 安装: go get github.com/garyburd/redigo/redis 二.简单连接Redis conn pa...

golang实现面向对象的封装、继承、多态

1. 基本介绍 面向对象程序设计(Object Oriented Programming,OOP)是一种计算机编程框架,尽可能的模拟人类的思维方式,使得软件的开发方法与过程尽可能接近人类认识世界、解决现实问题的方法和过程。 封装、继承、多态和抽象是面向对象的4个基本特征。 2. golang的面向对象类型 golang实现面向对象的两个关键类型是stru...

Golang Web入门(1):自顶向下理解Http服务器

摘要 由于Golang优秀的并发处理,很多公司使用Golang编写微服务。对于Golang来说,只需要短短几行代码就可以实现一个简单的Http服务器。加上Golang的协程,这个服务器可以拥有极高的性能。然而,正是因为代码过于简单,我们才应该去研究他的底层实现,做到会用,也知道为什么这么用。 在本文中,会以自顶向下的方式,从如何使用,到如何实现,一点点的分...