不想看長篇大論的,這里先給個結(jié)論,go的gc還不完善但也不算不靠譜,關(guān)鍵看怎么用,盡量不要創(chuàng)建大量對象,也盡量不要頻繁創(chuàng)建對象,這個道理其實在所有帶gc的編程語言也都通用。
想知道如何提前預(yù)防和解決問題的,請耐心看下去。
先介紹下我的情況,我們團(tuán)隊的項目《仙俠道》在7月15號第一次接受玩家測試,這個項目的服務(wù)端完全用Go語言開發(fā)的,游戲數(shù)據(jù)都放在內(nèi)存中由go 管理。
在上線測試后我對程序做了很多調(diào)優(yōu)工作,最初是穩(wěn)定性優(yōu)先,所以先解決的是內(nèi)存泄漏問題,主要靠memprof來定位問題,接著是進(jìn)一步提高性能,主要靠cpuprof和自己做的一些統(tǒng)計信息來定位問題。
調(diào)優(yōu)性能的過程中我從cpuprof的結(jié)果發(fā)現(xiàn)發(fā)現(xiàn)gc的scanblock調(diào)用占用的cpu竟然有40%多,于是我開始搞各種對象重用和盡量避免不必要的對象創(chuàng)建,效果顯著,CPU占用降到了10%多。
但我還是挺不甘心的,想繼續(xù)優(yōu)化看看。網(wǎng)上找資料時看到GOGCTRACE這個環(huán)境變量可以開啟gc調(diào)試信息的打印,于是我就在內(nèi)網(wǎng)測試服開啟了,每當(dāng)go執(zhí)行g(shù)c時就會打印一行信息,內(nèi)容是gc執(zhí)行時間和回收前后的對象數(shù)量變化。
我驚奇的發(fā)現(xiàn)一次gc要20多毫秒,我們服務(wù)器請求處理時間平均才33微秒,差了一個量級別呢。
于是我開始關(guān)心起gc執(zhí)行時間這個數(shù)值,它到底是一個恒定值呢?還是更數(shù)據(jù)多少有關(guān)呢?
我?guī)е蓡栐谕饩W(wǎng)玩家測試的服務(wù)器也開啟了gc追蹤,結(jié)果更讓我冒冷汗了,gc執(zhí)行時間竟然達(dá)到300多毫秒。go的gc是固定每兩分鐘執(zhí)行一次,每次執(zhí)行都是暫停整個程序的,300多毫秒應(yīng)該足以導(dǎo)致可感受到的響應(yīng)延遲。
所以縮短gc執(zhí)行時間就變得非常必要。從哪里入手呢?首先,可以推斷gc執(zhí)行時間跟數(shù)據(jù)量是相關(guān)的,內(nèi)網(wǎng)數(shù)據(jù)少外網(wǎng)數(shù)據(jù)多。其次,gc追蹤信息把對象數(shù)量當(dāng)成重點數(shù)據(jù)來輸出,估計掃描是按對象掃描的,所以對象多掃描時間長,對象少掃描時間短。
于是我便開始著手降低對象數(shù)量,一開始我嘗試用cgo來解決問題,由c申請和釋放內(nèi)存,這部分c創(chuàng)建的對象就不會被gc掃描了。
但是實踐下來發(fā)現(xiàn)cgo會導(dǎo)致原有的內(nèi)存數(shù)據(jù)操作出些詭異問題,例如一個對象明明初始化了,但還是讀到非預(yù)期的數(shù)據(jù)。另外還會引起go運行時報申請內(nèi)存死鎖的錯誤,我反復(fù)讀了go申請內(nèi)存的代碼,跟我直接用c的malloc完全都沒關(guān)聯(lián),實在是很詭異。
我只好暫時放棄cgo的方案,另外想了個法子。一個玩家有很多數(shù)據(jù),如果把非活躍玩家的數(shù)據(jù)序列化成一個字節(jié)數(shù)組,就等于把多個對象壓縮成了一個,這樣就可以大量減少對象數(shù)量。
我按這個思路用快速改了一版代碼,放到外網(wǎng)實際測試,對象數(shù)量從幾百萬降至幾十萬,gc掃描時間降至二十幾微秒。
效果不錯,但是要用玩家數(shù)據(jù)時要反序列化,這個消耗太大,還需要再想辦法。
于是我索性把內(nèi)存數(shù)據(jù)都改為結(jié)構(gòu)體和切片存放,之前用的是對象和單向鏈表,所以一條數(shù)據(jù)就會有一個對象對應(yīng),改為結(jié)構(gòu)體和結(jié)構(gòu)體切片,就等于把多個對象數(shù)據(jù)縮減下來。
結(jié)果如預(yù)期的一樣,內(nèi)存多消耗了一些,但是對象數(shù)量少了一個量級。
其實項目之初我就擔(dān)心過這樣的情況,那時候到處問人,對象多了會不會增加gc負(fù)擔(dān),導(dǎo)致gc時間過長,結(jié)果沒得到答案。
現(xiàn)在我填過這個坑了,可以確定的說,會。大家就不要再往這個坑跳了。
如果go的gc聰明一點,把老對象和新對象區(qū)別處理,至少在我這個應(yīng)用場景可以減少不必要的掃描,如果gc可以異步進(jìn)行不暫停程序,我才不在乎那幾百毫秒的執(zhí)行時間呢。
但是也不能完全怪go不完善,如果一開始我早點知道用GOGCTRACE來觀測,就可以比較早點發(fā)現(xiàn)問題從而比較根本的解決問題。但是既然用了,項目也上了,沒辦法大改,只能見招拆招了。
總結(jié)以下幾點給打算用go開發(fā)項目或已經(jīng)在用go開發(fā)項目的朋友:
1、盡早的用memprof、cpuprof、GCTRACE來觀察程序。
2、關(guān)注請求處理時間,特別是開發(fā)新功能的時候,有助于發(fā)現(xiàn)設(shè)計上的問題。
3、盡量避免頻繁創(chuàng)建對象(&abc{}、new(abc{})、make()),在頻繁調(diào)用的地方可以做對象重用。
4、盡量不要用go管理大量對象,內(nèi)存數(shù)據(jù)庫可以完全用c實現(xiàn)好通過cgo來調(diào)用。
手機(jī)回復(fù)打字好累,先寫到這里,后面再來補(bǔ)充案例的數(shù)據(jù)。
-----------------
轉(zhuǎn) 大概 2014 7 的golang版本 ,可能以后golang的版本會不斷進(jìn)步優(yōu)化