案例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
圖2
從圖1 和圖2 得知-sql map 有內存泄露 sql 也有內存泄露、 目前260w 的數據,每天跑N次。 每次會切割成M次子任務、 所以會泄露N*M次
由於通過火焰圖我們追蹤到了泄露的Func ,那麼我們直接定位到相應源碼去查看下·
從圖中可以看出、這個清空操作失效、因爲time.Now().Day() 永遠和w.FrequencyControlFormat.Day 一致。另外每次new 都是新的引用、新的地址、導致每次都會將數據庫數據全量load到map內存中,不會被釋放、
另外我們再通過debug/pprof 去查看下各項詳細情況
可以看出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)}
}