程序功能

此程序的主要功能是将文件中数据导入到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. ArrayList中删除null元素效率比较

    package test; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; i ...

  2. 洛谷$P4884$ 多少个1? 数论

    正解:$BSGS$ 解题报告: 传送门$QwQ$ 首先看到这个若干个一,发现不好表示,考虑两遍同时乘九加一,于是变成$10^n\equiv 9\cdot K+1(mod\ m)$ 昂然后不就是$bsg ...

  3. 用Eclipse和Tomcat搭建一个本地服务器

    服务器软件环境 Eclipse oxygen Tomcat 9.0 SQL Sever 2014 参考资料 https://blog.csdn.net/qq_21154101/article/deta ...

  4. ad域-iis

    环境准备: 1. win server 服务器安装完成 2.配置主机名 3.配置静态ip 安装ad域和iis 重启服务器 密码记住!!! 点击安装 把服务器的NDS设置成本机ip 重启完成 注意:ad ...

  5. 个人博客-vue-blog

    http://47.100.126.169/zmengBlog/

  6. java小项目之:植物大战僵尸,这个僵尸有点冷!内附素材源码

    Java小项目之:植物大战僵尸! <植物大战僵尸>是由PopCap Games开发的一款益智策略类单机游戏,于2009年5月5日发售,这款游戏可谓是无人不知无人不晓. 在我身边,上到40岁 ...

  7. 原生js中call、apply、bind的区别和相同点

    结论: 相同点: 1.都是js原生方法,改变函数中的this指向 2.都可以传递参数,第一个参数为 把this指向到哪里去,即目标元素 不同点: 1.call和apply都是主动触发,绑定后自动执行, ...

  8. .net core2.2上传文件总结

    总结一下.net core的上传文件操作,这里主要分上传到本地的也就是MVC的,另一种是上传到WebAPi的. 一.Web端 1.新建一个.net core mvc项目 2.这里的版本是.net co ...

  9. kubernetes concepts (一)

    Concepts The Concepts section helps you learn about the parts of the Kubernetes system and the abstr ...

  10. Scrapy定制起始请求

    Scrapy引擎来爬虫中取起始的URL 1.调用start_requests方法(父类),并获取返回值 2.将放回值变成迭代器,通过iter() 3.执行__next__()方法取值 4.把返回值全部 ...