Golang 踩坑分析

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

clipboard.png
圖2

clipboard.png

從圖1 和圖2 得知-sql map 有內存泄露 sql 也有內存泄露、 目前260w 的數據,每天跑N次。 每次會切割成M次子任務、 所以會泄露N*M次
由於通過火焰圖我們追蹤到了泄露的Func ,那麼我們直接定位到相應源碼去查看下·

clipboard.png

從圖中可以看出、這個清空操作失效、因爲time.Now().Day() 永遠和w.FrequencyControlFormat.Day 一致。另外每次new 都是新的引用、新的地址、導致每次都會將數據庫數據全量load到map內存中,不會被釋放、

另外我們再通過debug/pprof 去查看下各項詳細情況

clipboard.png
可以看出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)}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章