Golang中的CPU佔滿100%及解決方案

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)

解決

參考go buffer pool

  • 先創建一個buffer pool

  • Get

  • 用完再Put回去

  • 注意,最好在GetPut時加鎖。

是什麼原因導致了CPU開銷很大(重點)

當我們新建了一個有長度變量時,例如100byte的數組,那麼它在操作系統內存中是這樣展現的

memory.png

因此,當我們新建一個變量時,操作系統會在自己的運行內存裏開闢一塊內存給這個變量存數據用。當我們不需要這個數據時,或者說要刪除這個變量時,Golang會執行垃圾回收機制。

然而當Golang在執行垃圾回收時,操作系統會不斷對這些有或者沒有被引用的變量進行掃描,這中間涉及操作系統的算法,我們不用深究,但是,在執行這種算法時,會佔用CPU的資源,如果新開闢的變量和內存過多,就會導致系統不停的檢查是否有不需要引用的變量了,從而造成佔用CPU資源過多。

解決辦法

創建一個buffer pool

創建一個大的buffer pool,你需要內存時,向buffer pool獲取一下Get,用完不需要時再還回去Put

bufferpool.png

這樣做的好處是,操作系統每次檢查內存時,都只有一個buffer pool在引用,不增不減,於是也就減少CPU資源的消耗了。

打個比方

比如說操作系統就是一個土豪,借東西再換回來不收利息。它有一個很大的內存,周圍許多人都想找它去借(新聲明的變量並初始化),剛開始借的人只有十幾個,後面有上萬個,於是它要每天記錄誰借了多少內存出去,誰還沒有歸還,歸還的直接從記錄上把名字劃掉(垃圾回收)。後來操作系統煩了,於是就建了一個很大的內存池,夠所有人分批次借,只要借完及時歸還就行,而它每次去看這個內存池有沒有變小即可,省了不少精力。

而這個內存池就是go buffer pool的作用。

注意

建議給GetPut加鎖,防止多個協程同時借閱,造成競爭冒險。
這個CPU佔滿問題涉及Golang的垃圾回收機制,這塊是要點,一定要搞明白。

參考文章和資源

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章