Golang中的CPU佔滿100%及解決方案
有一個流媒體適配服務,出現了CPU
開銷很大的問題,一個服務把CPU
資源佔滿了,導致其他服務無法正常工作。
下面來詳細記錄發現bug
和解決的流程。
發現CPU開銷很大
掃描發現,是垃圾回收導致 CPU
使用上升 :
Time: Mar 22, 2019 at 5:52pm (CST)
Duration: 1mins, Total samples = 1.43mins (142.57%)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) tree
Showing nodes accounting for 83.13s, 97.11% of 85.60s total
Dropped 256 nodes (cum <= 0.43s)
----------------------------------------------------------+-------------
flat flat% sum% cum cum% calls calls% + context
----------------------------------------------------------+-------------
71.36s 99.86% | runtime.gcDrain
0.10s 0.14% | runtime.systemstack
48.56s 56.73% 56.73% 71.46s 83.48% | runtime.scanobject
11.86s 16.60% | runtime.heapBitsForObject
11.04s 15.45% | runtime.greyobject
----------------------------------------------------------+-------------
11.86s 99.92% | runtime.scanobject
11.87s 13.87% 70.60% 11.87s 13.87% | runtime.heapBitsForObject
----------------------------------------------------------+-------------
11.04s 100% | runtime.scanobject
11.02s 12.87% 83.47% 11.04s 12.90% | runtime.greyobject
----------------------------------------------------------+-------------
6.53s 95.05% | runtime.gosweepone.func1
0.34s 4.95% | runtime.(*mheap).alloc
4.34s 5.07% 88.54% 6.87s 8.03% | runtime.sweepone
2.53s 36.83% | runtime.(*mspan).sweep
----------------------------------------------------------+-------------
74.42s 100% | runtime.gcBgMarkWorker.func2
1.97s 2.30% 90.84% 74.42s 86.94% | runtime.gcDrain
71.36s 95.89% | runtime.scanobject
0.52s 0.7% | runtime.pollWork
----------------------------------------------------------+-------------
準備工作
之後考慮使用 buffer pool
,
// 這裏不再分配新的內存,而是從 buffer pool 裏面 GET
databuf = make([]byte, 100000)
解決
-
先創建一個
buffer pool
-
在
Get
它 -
用完再
Put
回去 -
注意,最好在
Get
和Put
時加鎖。
是什麼原因導致了CPU開銷很大(重點)
當我們新建了一個有長度變量時,例如100byte
的數組,那麼它在操作系統內存中是這樣展現的
因此,當我們新建一個變量時,操作系統會在自己的運行內存裏開闢一塊內存給這個變量存數據用。當我們不需要這個數據時,或者說要刪除這個變量時,Golang
會執行垃圾回收機制。
然而當Golang
在執行垃圾回收時,操作系統會不斷對這些有或者沒有被引用的變量進行掃描,這中間涉及操作系統的算法,我們不用深究,但是,在執行這種算法時,會佔用CPU
的資源,如果新開闢的變量和內存過多,就會導致系統不停的檢查是否有不需要引用的變量了,從而造成佔用CPU
資源過多。
解決辦法
創建一個buffer pool
創建一個大的buffer pool
,你需要內存時,向buffer pool
獲取一下Get
,用完不需要時再還回去Put
。
這樣做的好處是,操作系統每次檢查內存時,都只有一個buffer pool
在引用,不增不減,於是也就減少CPU
資源的消耗了。
打個比方
比如說操作系統就是一個土豪,借東西再換回來不收利息。它有一個很大的內存,周圍許多人都想找它去借(新聲明的變量並初始化),剛開始借的人只有十幾個,後面有上萬個,於是它要每天記錄誰借了多少內存出去,誰還沒有歸還,歸還的直接從記錄上把名字劃掉(垃圾回收)。後來操作系統煩了,於是就建了一個很大的內存池,夠所有人分批次借,只要借完及時歸還就行,而它每次去看這個內存池有沒有變小即可,省了不少精力。
而這個內存池就是go buffer pool
的作用。
注意
建議給Get
和Put
加鎖,防止多個協程同時借閱,造成競爭冒險。
這個CPU
佔滿問題涉及Golang
的垃圾回收機制,這塊是要點,一定要搞明白。