同步至 https://segmentfault.com/a/1190000020103403
案例1:Golang內(nèi)存泄露
## 影響情況##
服務(wù)A內(nèi)存泄露,造成服務(wù)器內(nèi)存不足诽凌,系統(tǒng)運(yùn)行的服務(wù)A 因?yàn)镺OM 被強(qiáng)制KILL 掉妻坝、導(dǎo)致任務(wù)丟失
##分析思路##
Golang 編寫的服務(wù)遇到OOM情況如何分析處理那?首先我們利用golang 自帶的pprof來分析尤溜。在main.go中 增加`
go func() {
if err := http.ListenAndServe("0.0.0.0:12345", nil); err != nil {
log.Println(err)
}
}()`
然后再結(jié)合火焰圖 去分析<以下是兩張火焰圖>
圖1
[圖片上傳失敗...(image-d6b713-1566194536453)]
圖2
[圖片上傳失敗...(image-4a2da9-1566194536453)]
從圖1 和圖2 得知-sql map 有內(nèi)存泄露 sql 也有內(nèi)存泄露仪召、 目前260w 的數(shù)據(jù)敷搪,每天跑N次循捺。 每次會切割成M次子任務(wù)、 所以會泄露N*M次
由于通過火焰圖我們追蹤到了泄露的Func 吼具,那么我們直接定位到相應(yīng)源碼去查看下·
[圖片上傳失敗...(image-9de4f0-1566194536453)]
從圖中可以看出僚纷、這個清空操作失效、因?yàn)閠ime.Now().Day() 永遠(yuǎn)和w.FrequencyControlFormat.Day 一致拗盒。另外每次new 都是新的引用怖竭、新的地址、導(dǎo)致每次都會將數(shù)據(jù)庫數(shù)據(jù)全量load到map內(nèi)存中陡蝇,不會被釋放痊臭、
另外我們再通過debug/pprof 去查看下各項(xiàng)詳細(xì)情況
[圖片上傳失敗...(image-ade1a2-1566194536453)]
可以看出goroutine 數(shù)量一直在增加,沒有被釋放掉。因?yàn)閏hannel 沒有主動關(guān)閉登夫、導(dǎo)致還會夯住對應(yīng)的goroutine,所以要注意channel 的使用要及時關(guān)閉广匙,否則也會造成資源的浪費(fèi)、
那么我們在分析一下并發(fā)安全的map 里面存儲的數(shù)據(jù)為什么是引用的數(shù)據(jù)庫的數(shù)據(jù)源地址
func (m *Map) Store(key, value interface{}) {
// 如果read存在這個鍵恼策,并且這個entry沒有被標(biāo)記刪除鸦致,嘗試直接寫入,寫入成功,則結(jié)束
// 第一次檢測
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確保元素沒有被標(biāo)記為刪除
// 判斷元素被標(biāo)識為刪除
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 復(fù)制一份到dirty map
if !read.amended {
m.dirtyLocked()
// 設(shè)置read.amended==true弧蝇,說明dirty map有數(shù)據(jù)
m.read.Store(readOnly{m: read.m, amended: true})
}
// 設(shè)置元素進(jìn)入dirty map,此時dirty map擁有read map和最新設(shè)置的元素
m.dirty[key] = newEntry(value)
}
// 解鎖折砸,有人認(rèn)為鎖的范圍有點(diǎn)大看疗,假設(shè)read map數(shù)據(jù)很大,那么執(zhí)行m.dirtyLocked()會耗費(fèi)花時間較多鞍爱,完全可以在操作dirty map時才加鎖鹃觉,這樣的想法是不對的专酗,因?yàn)閙.dirtyLocked()中有寫入操作
m.mu.Unlock()
}
//重點(diǎn)在這里 引用-
//導(dǎo)致forrange 數(shù)據(jù)庫的數(shù)據(jù)的時候 -數(shù)據(jù)庫的數(shù)據(jù)不能被釋放~
func newEntry(i interface{}) *entry {
return &entry{p: unsafe.Pointer(&i)}
}