這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
我們公司服務端都是在用Golang,每天幾百萬的UV,過億的PV一直沒啥問題。後來改了一次邏輯,首頁能展示一個列表,之前這個列表都沒有做過緩衝處理,一個是因為資料少,一共才一千多條;還有就是量不大。但是首頁加了入口之後出了問題,記憶體瘋漲,一般記憶體也就佔200M+,這還是包含In Memory緩衝的情況下,這下好傢夥,最高我見過佔用20G記憶體的情況,但是程式重啟一下又能好。當然這是後知後覺了,當時也沒把這兩個東西聯絡到一起。
完整的說一下事情的經過。
上線之後記憶體瘋漲,一開始會佔10G左右,不知道為啥,只能kill
掉。程式重啟之後短時間內也會維持在200M左右,但是過個4、5天又會漲上來,而這個過程中記憶體大小都沒有影響使用者使用。最後只能跟營運說,看見記憶體大了就kill
一次。。。
當時覺得記憶體這麼大,肯定是GC的問題,不曉得是哪裡記憶體流失了。我們用的是beego,我就用它封裝好的記憶體pprof
和CPU偵查工具各種查,實在是沒發現啥可疑的。毫無頭緒。
後來機緣巧合去看了Gopher China 2015大會的視頻,全程視頻都放在了慕課網,大家也可以去看看。都是業內用Golang的大佬的演講。推薦大家去看:
- Go 語言在遊戲項目的應用情況彙報,達達在GC方面在國內算是先驅了,很多人研究GC都是參考的他的資料。
- Go 語言構建高並發分布式系統實踐,Golang在360的應用。
- Go 1.4 runtime,這個是真大神,雨痕,把整個
runtime
講得很明白,我這種菜鳥都能聽的懂,想看記憶體配置原理就去看這個。
再後來,又發生了一件事情,我們大促的時候,線上出現了Can't create more than max_prepared_stmt_count statements (current value: 16382)
,資料庫查詢量太大,DBA把查詢數調成了30000都不行。DBA懷疑是我們沒有釋放,但是這都是用現成的orm寫的代碼,並且去看了源碼,確實釋放了。
最後沒轍,加緩衝。這下邪了,資料庫好了,再也沒有那個錯誤了,而且記憶體也好了,再也不亂漲了。今天又在開發人員頭條看到了一篇文章《golang gc 探究》,跟我們的情況很類似。這也就能說明,我們之前記憶體離奇瘋漲,是和Golang的GC機制有關。
下面說一下我對GC的理解。為了保證程式內記憶體的連續,Golang會申請一大塊記憶體(有時候,唯寫一個hello, world可能監控記憶體可能都會發現佔用記憶體比想象中的大)。當使用者的程式申請的記憶體大於之前預申請的記憶體時,runtime
會進行一次GC,並且將GC的閾值翻倍。也就是說,之前是超過10M時進行GC,那麼下一次GC就是超過20M才進行。此外,runtime
還支援定時GC。我們記憶體升高的原因,目前看來就是訪問量過大,資料庫訪問的時候導致GC閾值變大,回收頻率變低。而且在回收方面,Golang採用了一種拖延症策略,即使是被釋放的記憶體,runtime
也不會立刻把記憶體還給系統。這就導致了記憶體降不下來,一種記憶體流失的假象。
Golang在GC的時候會發生Stop the world
,整個程式會暫停,然後去標記整個記憶體裡面可以被回收的變數,標記完之後恢複程式執行,最後非同步得去回收記憶體。一般這個過程會達到20ms。標記可回收變數的時間取決於臨時變數的個數。臨時變數數量越多,掃描時間會越長。
所以目前GC的最佳化方式原則就是儘可能少的聲明臨時變數:
- 局部變數盡量複用;
- 如果局部變數過多,可以把這些變數放到一個大結構體裡面,這樣掃描的時候可以只掃描一個變數,回收掉它包含的很多記憶體;
Golang目前一直在最佳化GC,目前整體效果來看和大Java差不多,但是穩定性上面來看,還是不行。一般壓力測試第一波上面效果極差。
######參考文獻1. Go 的記憶體回收機制在實踐中有哪些需要注意的地方? - 達達
原文連結:Golang的記憶體回收,轉載請註明來源!