使用pprof工具分析内存情况

假设运行host的ip为192.168.2.10
在程序入口添加以下代码(注意添加后会影响性能,不建议在线上使用):

import _ "net/http/pprof"

go func() {
log.Println(http.ListenAndServe("0.0.0.0:6060", nil))
}()

然后在本地开一个终端,开启采样分析工具的web服务:

go tool pprof -seconds=10 -http=:9999 http://192.168.2.10:6060/debug/pprof/heap

如果本地无法访问host网络的192.168.2.10地址(比如存在防火墙,或者网络隔离
可在host上生成一个采样

curl http://localhost:6060/debug/pprof/heap?seconds=60 > heap

然后传输heap文件到本地,执行:

go tool pprof heap

之后将会自动打开一个本地网页

点击View->Top 可见主要是前两处调用占用了不合常规的大量的内存空间,因为这两处函数不该处理这么多的数据

upload successful

** 切换FlameGraph(new)可见调用层级 **

upload successful

** 切换Graph可见调用图 **

upload successful

alloc 和inuse的区别

“Allocate”(分配)和”Inuse”(正在使用)是与内存管理相关的两个术语,通常用于描述程序在运行时对内存的使用情况。它们之间的区别在于以下几点:

  1. Allocate(分配):这通常指的是程序在运行时向操作系统请求分配一定数量的内存空间。这个内存可能还没有被程序实际使用,但已经被分配给了程序。在程序运行期间,可能会多次进行内存分配,但不一定所有分配的内存都会被实际使用。

  2. Inuse(正在使用):这指的是程序实际上正在使用的内存量。这包括已经分配给程序并且程序正在利用的内存。在运行过程中,程序可能会动态地分配和释放内存,因此”Inuse”会在不同的时间点发生变化。

在一些内存分析工具中,你可能会看到”Allocate”和”Inuse”两者之间的区别。例如,当程序开始执行时,可能会分配一定量的内存,但直到程序执行某些操作,这些内存才会真正被使用。因此,在某些情况下,”Allocate”可能会大于”Inuse”,因为一部分分配的内存可能尚未被程序利用。

总体而言,”Allocate”表示分配给程序的内存总量,而”Inuse”表示程序当前实际使用的内存量。

upload successful

找到泄露点之后就可以分析是哪里泄露了内存,情况分以下几种:
参考
https://go101.org/article/memory-leaking.html

泄露点:

  1. Redis管道未关闭:根据pprof可见redis相关pipeline操作持续占用大量的内存,检查相关代码发现管道在使用没有关闭
    upload successful
    添加函数退出后的关闭操作:

    upload successful

  2. channel未关闭:ppro可见NewFastStep相关调用持续占用大量内存,调用链上存在未关闭的chan,channel在写入完毕就应该关闭,此时并不影响读取

    func NewFastStepQueue(defaultTopic, defaultQueue, dfsAddr string, db, dbCow mongodb.CommonClient, rds redis.Client) *FastStepQueue {
    ctx, cancel := context.WithCancel(context.Background())
    return &FastStepQueue{
    db: db,
    ...
    FastStep: make(chan TaskStep, 100),
    Ctx: ctx,
    cancel: cancel,
    }
    }

    func (queue *FastStepQueue) AssignFastSteps(fastSteps []TaskStep) {
    for _, task := range fastSteps {
    queue.FastStep <- task
    blog.V(3).Infof("快速任务已经添加到队列:%v", task)
    }
    go queue.SendFastStepsQueue()
    }

    upload successful
       添加关闭channel代码:
       

    close(queue.FastStep)

    upload successful

优化之后2小时:

upload successful

upload successful

优化之后24小时:

NewMQ这里仍有不少优化空间

upload successful

经过排查发现有两处泄露

  • 未关闭的channel导致
    添加close chanel的操作

upload successful

  • 部分情况下,走到某些逻辑中返回后groutine没有结束(context cancel没有触发导致)

    删除在各个逻辑中触发的cancel函数,改为使用defer添加相应的处理,确保在函数退出时结束context
    upload successful

再次优化之后72小时后

upload successful

已经解决了内存泄露

监控图表中可见更新后,基本上不存在泄露

upload successful30.png)