最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

golang進(jìn)程內(nèi)存控制避免docker內(nèi)oom

 更新時(shí)間:2022年10月21日 09:39:33   作者:硅基生命  
這篇文章主要為大家介紹了golang進(jìn)程內(nèi)存控制避免docker內(nèi)oom示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

背景

golang版本:1.16

之前遇到的問(wèn)題,docker啟動(dòng)時(shí)禁用了oom-kill(kill后服務(wù)受損太大),導(dǎo)致golang內(nèi)存使用接近docker上限后,進(jìn)程會(huì)hang住,不響應(yīng)任何請(qǐng)求,debug工具也無(wú)法attatch。

前文分析見(jiàn):golang進(jìn)程在docker中OOM后hang住問(wèn)題

本文主要嘗試給出解決方案

測(cè)試程序

測(cè)試程序代碼如下,協(xié)程h.allocate每秒檢查內(nèi)存是否達(dá)到800MB,未達(dá)到則申請(qǐng)內(nèi)存,協(xié)程h.clear每秒檢查內(nèi)存是否超過(guò)800MB的80%,超過(guò)則釋放掉超出部分,模擬通常的業(yè)務(wù)程序頻繁進(jìn)行內(nèi)存申請(qǐng)和釋放的邏輯。程序通過(guò)http請(qǐng)求127.0.0.1:6060觸發(fā)開始執(zhí)行方便debug。

docker啟動(dòng)時(shí)加--memory 1G --memory-reservation 1G --oom-kill-disable=true參數(shù)限制總內(nèi)存1G并關(guān)閉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          // 開始進(jìn)行內(nèi)存申請(qǐng)釋放
   total        int32           // 4kB內(nèi)存總個(gè)數(shù)
   count        int             // 4KB內(nèi)存最大個(gè)數(shù)
   ratio        float64         // 內(nèi)存數(shù)達(dá)到count*ratio后釋放多的部分
   bytesBuffers [][]byte        // 內(nèi)存池
   locks        []*sync.RWMutex // 每個(gè)4kb內(nèi)存一個(gè)鎖減少競(jìng)爭(zhēng)
   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) // 觸發(fā)開始內(nèi)存申請(qǐng)釋放
}
func (h *handler) started() bool {
   return atomic.LoadUint32(&h.start) == 1
}
// 每s檢查內(nèi)存未達(dá)到count個(gè)則補(bǔ)足
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: %dKB\n", atomic.LoadInt32(&h.total)*arraySize/1024)
         }
      }
   }()
}
// 每s檢查內(nèi)存超過(guò)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: %dKB\n", 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...%d\n", d[0])
         }()
      }
   }()
}
// 等待啟動(dòng)
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()
}

程序執(zhí)行一段時(shí)間后rss占用即達(dá)到1G,程序不再響應(yīng)請(qǐng)求,docker無(wú)法通過(guò)bash連接上,已經(jīng)連接的bash執(zhí)行命令顯示錯(cuò)誤bash: fork: Cannot allocate memory

一、為gc預(yù)留空間方案

之前的分析中,hang住的地方是調(diào)用mmap,golang內(nèi)的堆棧是gc stw后的mark階段,所以最開始的解決方法是想在stw之前預(yù)留100MB空間,stw后釋放該部分空間給操作系統(tǒng),改動(dòng)如下:

但是進(jìn)程同樣會(huì)hang住,debug單步調(diào)試發(fā)現(xiàn)存在三種情況

  • 未觸發(fā)gc(是因?yàn)間c的步長(zhǎng)參數(shù)默認(rèn)為100%,下一次gc觸發(fā)的時(shí)機(jī)默認(rèn)是內(nèi)存達(dá)到上次gc的兩倍);
  • gc的stw之前就阻塞住,多數(shù)在gcBgMarkStartWorkers函數(shù)啟動(dòng)新的goroutine時(shí)陷入阻塞;
  • gc的stw后mark prepare階段阻塞,即前文分析中的,申請(qǐng)新的workbuf時(shí)在mmap時(shí)阻塞;

可見(jiàn),預(yù)留內(nèi)存的方式只能對(duì)第3種情況有改善,增加了預(yù)留內(nèi)存后多數(shù)為第2種情況阻塞。

從解決問(wèn)題的角度看,預(yù)留內(nèi)存,是讓gc去適配內(nèi)存達(dá)到上限后系統(tǒng)調(diào)用阻塞的情況,對(duì)于其他情況gc反而更差了,因?yàn)橛蓄~外的內(nèi)存和cpu開銷。更何況因?yàn)榈?種情況的存在,導(dǎo)致gc的修改無(wú)法面面俱到。

而且即使第2種情況創(chuàng)建g不阻塞,創(chuàng)建g后仍然需要找到合適的m執(zhí)行,但因?yàn)橐延械膍都會(huì)因?yàn)橄到y(tǒng)調(diào)用被阻塞,而創(chuàng)建新的m即新的線程,又會(huì)被阻塞在內(nèi)存申請(qǐng)上。所以這是不光golang會(huì)遇到的問(wèn)題,即使用其他語(yǔ)言寫也會(huì)有這種問(wèn)題。在這種環(huán)境下運(yùn)行的進(jìn)程,必須對(duì)自身的內(nèi)存大小做嚴(yán)格控制。

二、調(diào)整gc參數(shù)

通過(guò)第一種方案的嘗試,我們需要轉(zhuǎn)換角度,結(jié)合實(shí)際使用場(chǎng)景做適配, 避免影響golang運(yùn)行機(jī)制。限制條件主要有:

  • 進(jìn)程會(huì)使用較多內(nèi)存
  • 進(jìn)程的使用有上限, 達(dá)到上限后系統(tǒng)調(diào)用會(huì)阻塞

需要讓進(jìn)程控制內(nèi)存上限,同時(shí)在達(dá)到上限前多觸發(fā)gc。解決方式如下:

  • 用內(nèi)存池。測(cè)試程序中的allocate和clear的邏輯,實(shí)際上就是實(shí)現(xiàn)了一個(gè)內(nèi)存池,控制總的內(nèi)存在640~800MB之間波動(dòng)。
  • 增加gc頻率。程序啟動(dòng)時(shí)加環(huán)境變量GOGC=12,控制gc步長(zhǎng)在12%,例如內(nèi)存池達(dá)到800MB時(shí),會(huì)在800*112%=896MB時(shí)觸發(fā)gc,避免內(nèi)存達(dá)到1G上限。

實(shí)測(cè)進(jìn)程內(nèi)存在900MB以下波動(dòng),沒(méi)有hang住。

以上就是golang進(jìn)程內(nèi)存控制避免docker內(nèi)oom的詳細(xì)內(nèi)容,更多關(guān)于golang進(jìn)程避免docker oom的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Go Run, Go Build, Go Install的區(qū)別

    Go Run, Go Build, Go Install的區(qū)別

    本文深入探討Go語(yǔ)言中g(shù)orun、gobuild和goinstall三個(gè)常用命令的功能區(qū)別和適用場(chǎng)景,文中通過(guò)具體代碼示例,詳細(xì)解釋了各命令的使用方式及其應(yīng)用場(chǎng)景,幫助開發(fā)者高效利用這些工具
    2024-10-10
  • Golang上下文Context的常見(jiàn)應(yīng)用場(chǎng)景

    Golang上下文Context的常見(jiàn)應(yīng)用場(chǎng)景

    Golang?context主要用于定義超時(shí)取消,取消后續(xù)操作,在不同操作中傳遞值。本文通過(guò)簡(jiǎn)單易懂的示例進(jìn)行說(shuō)明,感興趣的可以了解一下
    2023-04-04
  • GO實(shí)現(xiàn)協(xié)程池管理的方法

    GO實(shí)現(xiàn)協(xié)程池管理的方法

    這篇文章給大家介紹GO實(shí)現(xiàn)協(xié)程池管理的方法,分別使用channel實(shí)現(xiàn)協(xié)程池和消費(fèi)者模式實(shí)現(xiàn)協(xié)程池,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友參考下吧
    2021-07-07
  • golang time包做時(shí)間轉(zhuǎn)換操作

    golang time包做時(shí)間轉(zhuǎn)換操作

    這篇文章主要介紹了golang time包做時(shí)間轉(zhuǎn)換操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-12-12
  • go語(yǔ)言寫的簡(jiǎn)要數(shù)據(jù)同步工具詳解

    go語(yǔ)言寫的簡(jiǎn)要數(shù)據(jù)同步工具詳解

    作為go-etl工具的作者,想要安利一下這個(gè)小巧的數(shù)據(jù)同步工具,它在同步百萬(wàn)級(jí)別的數(shù)據(jù)時(shí)表現(xiàn)極為優(yōu)異,基本能在幾分鐘完成數(shù)據(jù)同步,這篇文章主要介紹了go語(yǔ)言寫的簡(jiǎn)要數(shù)據(jù)同步工具,需要的朋友可以參考下
    2024-07-07
  • go語(yǔ)言中使用timer的常用方式

    go語(yǔ)言中使用timer的常用方式

    這篇文章主要介紹了go語(yǔ)言中使用timer的常用方式,實(shí)例分析了三種常用的使用timer的方法,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-03-03
  • Go語(yǔ)言基于viper的conf庫(kù)進(jìn)行配置文件解析

    Go語(yǔ)言基于viper的conf庫(kù)進(jìn)行配置文件解析

    在現(xiàn)代軟件開發(fā)中,配置文件是不可或缺的一部分,如何高效地將這些格式解析到 Go 結(jié)構(gòu)體中,一直是開發(fā)者的痛點(diǎn),下面我們來(lái)看看如何使用conf進(jìn)行配置文件解析吧
    2025-03-03
  • vscode中安裝Go插件和配置Go環(huán)境詳細(xì)步驟

    vscode中安裝Go插件和配置Go環(huán)境詳細(xì)步驟

    要在VSCode中配置Go語(yǔ)言插件,首先需要確保你的電腦已經(jīng)安裝了Go環(huán)境和最新版本的VSCode,這篇文章主要給大家介紹了關(guān)于vscode中安裝Go插件和配置Go環(huán)境的相關(guān)資料,需要的朋友可以參考下
    2024-01-01
  • 詳解Go?語(yǔ)言如何通過(guò)測(cè)試保證質(zhì)量

    詳解Go?語(yǔ)言如何通過(guò)測(cè)試保證質(zhì)量

    這篇文章主要為大家介紹了Go?語(yǔ)言如何通過(guò)測(cè)試保證質(zhì)量詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-08-08
  • Go類型斷言提取測(cè)試接口值動(dòng)態(tài)類型及靜態(tài)轉(zhuǎn)換確保類型接口編譯安全

    Go類型斷言提取測(cè)試接口值動(dòng)態(tài)類型及靜態(tài)轉(zhuǎn)換確保類型接口編譯安全

    這篇文章主要為大家介紹了Go類型斷言提取測(cè)試接口值動(dòng)態(tài)類型及靜態(tài)轉(zhuǎn)換確保類型實(shí)現(xiàn)特定接口的編譯時(shí)安全性詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-10-10

最新評(píng)論

尖扎县| 南阳市| 任丘市| 蒲江县| 台东市| 建平县| 安溪县| 法库县| 合阳县| 竹山县| 德惠市| 景宁| 武隆县| 尉氏县| 安庆市| 新巴尔虎左旗| 酉阳| 恩施市| 绥中县| 南澳县| 稷山县| 会同县| 政和县| 舒兰市| 咸宁市| 宁陕县| 太湖县| 隆安县| 久治县| 盱眙县| 洛宁县| 绵竹市| 且末县| 上虞市| 郴州市| 顺义区| 祁门县| 海阳市| 织金县| 威信县| 交城县|