<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
golang版本:1.16
之前遇到的問題,docker啟動時禁用了oom-kill(kill後服務受損太大),導致golang記憶體使用接近docker上限後,程序會hang住,不響應任何請求,debug工具也無法attatch。
前文分析見:golang程序在docker中OOM後hang住問題
本文主要嘗試給出解決方案
測試程式程式碼如下,協程h.allocate每秒檢查記憶體是否達到800MB,未達到則申請記憶體,協程h.clear每秒檢查記憶體是否超過800MB的80%,超過則釋放掉超出部分,模擬通常的業務程式頻繁進行記憶體申請和釋放的邏輯。程式通過http請求127.0.0.1:6060觸發開始執行方便debug。
docker啟動時加--memory 1G --memory-reservation 1G --oom-kill-disable=true
引數限制總記憶體1G並關閉oom-kill
package main import ( "fmt" "math/rand" "net/http" _ "net/http/pprof" "sync" "sync/atomic" "time" ) const ( maxBytes = 800 * 1024 * 1024 // 800MB arraySize = 4 * 1024 ) type handler struct { start uint32 // 開始進行記憶體申請釋放 total int32 // 4kB記憶體總個數 count int // 4KB記憶體最大個數 ratio float64 // 記憶體數達到count*ratio後釋放多的部分 bytesBuffers [][]byte // 記憶體池 locks []*sync.RWMutex // 每個4kb記憶體一個鎖減少競爭 wg *sync.WaitGroup } func newHandler(count int, ratio float64) *handler { h := &handler{ count: count, bytesBuffers: make([][]byte, count), locks: make([]*sync.RWMutex, count), wg: &sync.WaitGroup{}, ratio: ratio, } for i := range h.locks { h.locks[i] = &sync.RWMutex{} } return h } func (h *handler) ServeHTTP(writer http.ResponseWriter, request *http.Request) { atomic.StoreUint32(&h.start, 1) // 觸發開始記憶體申請釋放 } func (h *handler) started() bool { return atomic.LoadUint32(&h.start) == 1 } // 每s檢查記憶體未達到count個則補足 func (h *handler) allocate() { h.wg.Add(1) go func() { defer h.wg.Done() ticker := time.NewTicker(time.Second) for range ticker.C { for i := range h.bytesBuffers { h.locks[i].Lock() if h.bytesBuffers[i] == nil { h.bytesBuffers[i] = make([]byte, arraySize) h.bytesBuffers[i][0] = 'a' atomic.AddInt32(&h.total, 1) } h.locks[i].Unlock() fmt.Printf("allocated size: %dKBn", atomic.LoadInt32(&h.total)*arraySize/1024) } } }() } // 每s檢查記憶體超過count*ratio將超出的部分釋放掉 func (h *handler) clear() { h.wg.Add(1) go func() { defer h.wg.Done() ticker := time.NewTicker(time.Second) for range ticker.C { diff := int(atomic.LoadInt32(&h.total)) - int(float64(h.count)*h.ratio) tmp := diff for diff > 0 { i := rand.Intn(h.count) h.locks[i].RLock() if h.bytesBuffers[i] == nil { h.locks[i].RUnlock() continue } h.locks[i].RUnlock() h.locks[i].Lock() if h.bytesBuffers[i] == nil { h.locks[i].Unlock() continue } h.bytesBuffers[i] = nil h.locks[i].Unlock() atomic.AddInt32(&h.total, -1) diff-- } fmt.Printf("free size: %dKB, left size: %dKBn", tmp*arraySize/1024, atomic.LoadInt32(&h.total)*arraySize/1024) } }() } // 每s列印紀錄檔檢查是否阻塞 func (h *handler) print() { h.wg.Add(1) go func() { defer h.wg.Done() ticker := time.NewTicker(time.Second) for range ticker.C { go func() { d := make([]byte, 1024) // trigger gc d[0] = 1 fmt.Printf("running...%dn", d[0]) }() } }() } // 等待啟動 func (h *handler) wait() { h.wg.Add(1) go func() { defer h.wg.Done() addr := "127.0.0.1:6060" // trigger to start err := http.ListenAndServe(addr, h) if err != nil { fmt.Printf("failed to listen on %s, %+v", addr, err) } }() for !h.started() { time.Sleep(time.Second) fmt.Printf("waiting...n") } } // 等待退出 func (h *handler) waitDone() { h.wg.Wait() } func main() { go func() { addr := "127.0.0.1:6061" // debug _ = http.ListenAndServe(addr, nil) }() h := newHandler(maxBytes/arraySize, 0.8) h.wait() h.allocate() h.clear() h.print() h.waitDone() }
程式執行一段時間後rss佔用即達到1G,程式不再響應請求,docker無法通過bash連線上,已經連線的bash執行命令顯示錯誤bash: fork: Cannot allocate memory
之前的分析中,hang住的地方是呼叫mmap,golang內的堆疊是gc stw後的mark階段,所以最開始的解決方法是想在stw之前預留100MB空間,stw後釋放該部分空間給作業系統,改動如下:
但是程序同樣會hang住,debug單步偵錯發現存在三種情況
可見,預留記憶體的方式只能對第3種情況有改善,增加了預留記憶體後多數為第2種情況阻塞。
從解決問題的角度看,預留記憶體,是讓gc去適配記憶體達到上限後系統呼叫阻塞的情況,對於其他情況gc反而更差了,因為有額外的記憶體和cpu開銷。更何況因為第2種情況的存在,導致gc的修改無法面面俱到。
而且即使第2種情況建立g不阻塞,建立g後仍然需要找到合適的m執行,但因為已有的m都會因為系統呼叫被阻塞,而建立新的m即新的執行緒,又會被阻塞在記憶體申請上。所以這是不光golang會遇到的問題,即使用其他語言寫也會有這種問題。在這種環境下執行的程序,必須對自身的記憶體大小做嚴格控制。
通過第一種方案的嘗試,我們需要轉換角度,結合實際使用場景做適配, 避免影響golang執行機制。限制條件主要有:
需要讓程序控制記憶體上限,同時在達到上限前多觸發gc。解決方式如下:
實測程序記憶體在900MB以下波動,沒有hang住。
以上就是golang程序記憶體控制避免docker內oom的詳細內容,更多關於golang程序避免docker oom的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45