程序功能

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

【问题描述】

服务器内存每隔一段时间会耗尽

【问题分析】

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

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

工具的使用与思路:

1)先修改源代码

2)安装工具观察

3)根据工具抓取的现象进行分析

4)修复内存缺陷代码, 再根据分析结果修复内存泄漏的地方

5)发布代码进行再跟踪分析

1)修改代码:

使用这个工具前需要在代码中写几行代码,以便能使用这个工具的来收集数据。



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 ~]





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

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的调试发生,真正的原因是gc内存释放不够及时,存在滞后性(通过其他服务器观察发现,当压力小的时候,内存是可以正常释放的)。

所以最佳实践还是,在涉及到golang中使用大对象或者频繁创建内存的时候,要采用将对象设置能obj = nil 的方式,告知gc 我已经确实不再使用该内存块了,以便gc快速的回收,减少迭代gc。

另外,这种方式是可以应用到如java,c# 等语言身上的,它们都存在类似的问题。

记一次golang的内存泄露的更多相关文章

  1. 记一次Java的内存泄露分析

    当前环境 jdk == 1.8 httpasyncclient == 4.1.3 代码地址 git 地址:https://github.com/jasonGeng88/java-network-pro ...

  2. 记一次调试python内存泄露的问题

    转载:http://www.jianshu.com/p/2d06a1a01cc3 这两天由于公司需要, 自己编写了一个用于接收dicom文件(医学图像文件)的server. 经过各种coding-de ...

  3. 记一次golang内存泄露

    记一次golang内存泄露 最近在QA环境上验证功能时,发现机器特别卡,查看系统内存,发现可用(available)内存仅剩200多M,通过对进程耗用内存进行排序,发现有一个名为application ...

  4. [golang]golang time.After内存泄露问题分析

    无意中看到一篇文章说,当在for循环里使用select + time.After的组合时会产生内存泄露,于是进行了复现和验证,以此记录 内存泄露复现 问题复现测试代码如下所示: package mai ...

  5. 解Bug之路-记一次JVM堆外内存泄露Bug的查找

    解Bug之路-记一次JVM堆外内存泄露Bug的查找 前言 JVM的堆外内存泄露的定位一直是个比较棘手的问题.此次的Bug查找从堆内内存的泄露反推出堆外内存,同时对物理内存的使用做了定量的分析,从而实锤 ...

  6. golang kafka clinet 内存泄露问题处理

    go 内存泄露 新版本服务跑上一天内存占用20g,显然是内存泄露 内存泄露的问题难在定位 技术上的定位 主要靠 pprof 生成统计文件 之前写web项目 基于net/http/pprof 可以看到运 ...

  7. 实战Go内存泄露【转】

    最近解决了我们项目中的一个内存泄露问题,事实再次证明pprof是一个好工具,但掌握好工具的正确用法,才能发挥好工具的威力,不然就算你手里有屠龙刀,也成不了天下第一,本文就是带你用pprof定位内存泄露 ...

  8. Android开发笔记——常见BUG类型之内存泄露与线程安全

    本文内容来源于最近一次内部分享的总结,没来得及详细整理,见谅. 本次分享主要对内存泄露和线程安全这两个问题进行一些说明,内部代码扫描发现的BUG大致分为四类:1)空指针:2)除0:3)内存.资源泄露: ...

  9. golang的内存模型与new()与make()

    要彻底理解new()与make()的区别, 最好从内存模型入手. golang属于c family, 而c程序在unix的内在模型: |低地址|text|data|bss|heap-->|unu ...

随机推荐

  1. 清晰架构(Clean Architecture)的Go微服务: 程序容器(Application Container)

    清晰架构(Clean Architecture)的一个理念是隔离程序的框架,使框架不会接管你的应用程序,而是由你决定何时何地使用它们.在本程序中,我特意不在开始时使用任何框架,因此我可以更好地控制程序 ...

  2. mybatis两种方式

    // 1.传统的使用 statementId方式 //获取详情 sysApiDocumentMode = template.selectOne("oaApiDocument.getProje ...

  3. cometoj 茶颜悦色|扫描线+懒惰标记

    传送门 题目描述 茶颜悦色也太好喝了!鸡尾酒在长沙的各种茶颜悦色的店铺中流连忘返.他发现长沙有炒鸡多的茶颜悦色店,走两步就能遇到一家. “方圆一公里能有十家茶颜悦色!”鸡尾酒感叹了起来. 于是他想到了 ...

  4. C# 获取WebBrowser内容的高度

    int webHeight =0; WebBrowser web =new WebBrowser(); web.Navigate("about:blank"); while (we ...

  5. 3 分钟带你深入了解 Cookie、Session、Token

    经常会有用户咨询,CDN 是否会传递 Cookie 信息,是否会对源站 Session 有影响,Token 的防盗链配置为什么总是配置失败?为此,我们就针对 Cookie.Session 和 Toke ...

  6. 使用java做一个能赚钱的微信群聊机器人(2020年基于PC端协议最新可用版)

    前言 微信群机器人,主要用来管理群聊,提供类似天气查询.点歌.机器人聊天等用途. 由于微信将web端的协议封杀后,很多基于http协议的群聊机器人都失效了,所以这里使用基于PC端协议的插件来实现. 声 ...

  7. 搭建个人OpenAPI

    简介 OpenAPI Open API 即开放 API,也称开放平台. 所谓的开放 API(OpenAPI)是服务型网站常见的一种应用,网站的服务商将自己的网站服务封装成一系列 API(Applica ...

  8. Spring Boot2 系列教程(二十一) | 自动配置原理

    微信公众号:一个优秀的废人.如有问题,请后台留言,反正我也不会听. 前言 这个月过去两天了,这篇文章才跟大家见面,最近比较累,大家见谅下.下班后闲着无聊看了下 SpringBoot 中的自动配置,把我 ...

  9. svn或git 提交文件排除

    也可以参考  https://blog.csdn.net/chenmintong/article/details/79725324 乌龟git 过滤掉忽略文件(首先右键 某文件 删除并添加到忽略列表 ...

  10. Appium自动化测试框架研究(2)——搭建IOS环境

    今天的文章讲iOS的Appium环境搭建. 对于iOS而言,只能在Mac笔记本上安装Appium,以及所需要的各种组件. 也许有人会问,能否在Windows系统上使用Appium测试iOS手机,这不就 ...