42

Golang 踩坑分析

 4 years ago
source link: https://www.tuicool.com/articles/QRBNJbq
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

案例1:Golang内存泄露

## 影响情况##
服务A内存泄露,造成服务器内存不足,系统运行的服务A 因为OOM 被强制KILL 掉、导致任务丢失

##分析思路##
Golang 编写的服务遇到OOM情况如何分析处理那?首先我们利用golang 自带的pprof来分析。在main.go中 增加`
    go func() {
        if err := http.ListenAndServe("0.0.0.0:12345", nil); err != nil {
            log.Println(err)
        }
    }()`

然后再结合火焰图 去分析<以下是两张火焰图>

图1

BJ736fy.png!web

图2

Vb2AfmF.png!web

从图1 和图2 得知-sql map 有内存泄露 sql 也有内存泄露、 目前260w 的数据,每天跑N次。 每次会切割成M次子任务、 所以会泄露N*M次

由于通过火焰图我们追踪到了泄露的Func ,那么我们直接定位到相应源码去查看下·

UrmI3eI.png!web

从图中可以看出、这个清空操作失效、因为time.Now().Day() 永远和w.FrequencyControlFormat.Day 一致。另外每次new 都是新的引用、新的地址、导致每次都会将数据库数据全量load到map内存中,不会被释放、

另外我们再通过debug/pprof 去查看下各项详细情况

7jAVnyE.png!web

可以看出goroutine 数量一直在增加,没有被释放掉。因为channel 没有主动关闭、导致还会夯住对应的goroutine,所以要注意channel 的使用要及时关闭,否则也会造成资源的浪费、

那么我们在分析一下并发安全的map 里面存储的数据为什么是引用的数据库的数据源地址

func (m *Map) Store(key, value interface{}) {
    // 如果read存在这个键,并且这个entry没有被标记删除,尝试直接写入,写入成功,则结束
    // 第一次检测
    read, _ := m.read.Load().(readOnly)
    if e, ok := read.m[key]; ok && e.tryStore(&value) {
        return
    }
    // dirty map锁
    m.mu.Lock()
    // 第二次检测
    read, _ = m.read.Load().(readOnly)
    if e, ok := read.m[key]; ok {
        // unexpungelocc确保元素没有被标记为删除
        // 判断元素被标识为删除
        if e.unexpungeLocked() {
            // 这个元素之前被删除了,这意味着有一个非nil的dirty,这个元素不在里面.
            m.dirty[key] = e
        }
        // 更新read map 元素值
        e.storeLocked(&value)
    } else if e, ok := m.dirty[key]; ok {
        // 此时read map没有该元素,但是dirty map有该元素,并需修改dirty map元素值为最新值
        e.storeLocked(&value)
    } else {
        // read.amended==false,说明dirty map为空,需要将read map 复制一份到dirty map
        if !read.amended {
            m.dirtyLocked()
            // 设置read.amended==true,说明dirty map有数据
            m.read.Store(readOnly{m: read.m, amended: true})
        }
        // 设置元素进入dirty map,此时dirty map拥有read map和最新设置的元素
        m.dirty[key] = newEntry(value)
    }
    // 解锁,有人认为锁的范围有点大,假设read map数据很大,那么执行m.dirtyLocked()会耗费花时间较多,完全可以在操作dirty map时才加锁,这样的想法是不对的,因为m.dirtyLocked()中有写入操作
    m.mu.Unlock()
}
//重点在这里 引用-
//导致forrange 数据库的数据的时候 -数据库的数据不能被释放~
func newEntry(i interface{}) *entry {
   return &entry{p: unsafe.Pointer(&i)}
}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK