Golang踩坑分析

ddu_sw · · 804 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

同步至 https://segmentfault.com/a/1190000020103403

案例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

image

[图片上传失败...(image-d6b713-1566194536453)]

图2

image

[图片上传失败...(image-4a2da9-1566194536453)]

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

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

image

[图片上传失败...(image-9de4f0-1566194536453)]

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

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

image

[图片上传失败...(image-ade1a2-1566194536453)]

可以看出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)}

}


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:ddu_sw

查看原文:Golang踩坑分析

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

804 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传