不会飞的章鱼

熟能生巧,勤能补拙;念念不忘,必有回响。

Golang中的CPU占满100%及解决方案

有一个流媒体适配服务,出现了CPU开销很大的问题,一个服务把CPU资源占满了,导致其他服务无法正常工作。
下面来详细记录发现bug和解决的流程。

发现CPU开销很大

扫描发现,是垃圾回收导致 CPU 使用上升 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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,

1
2
   // 这里不再分配新的内存,而是从 buffer pool 里面 GET 
databuf = make([]byte, 100000)

解决

参考go buffer pool

  • 先创建一个buffer pool

  • Get

  • 用完再Put回去

  • 注意,最好在GetPut时加锁。

是什么原因导致了CPU开销很大(重点)

当我们新建了一个有长度变量时,例如100byte的数组,那么它在操作系统内存中是这样展现的

因此,当我们新建一个变量时,操作系统会在自己的运行内存里开辟一块内存给这个变量存数据用。当我们不需要这个数据时,或者说要删除这个变量时,Golang会执行垃圾回收机制。

然而当Golang在执行垃圾回收时,操作系统会不断对这些有或者没有被引用的变量进行扫描,这中间涉及操作系统的算法,我们不用深究,但是,在执行这种算法时,会占用CPU的资源,如果新开辟的变量和内存过多,就会导致系统不停的检查是否有不需要引用的变量了,从而造成占用CPU资源过多。

解决办法

创建一个buffer pool

创建一个大的buffer pool,你需要内存时,向buffer pool获取一下Get,用完不需要时再还回去Put

这样做的好处是,操作系统每次检查内存时,都只有一个buffer pool在引用,不增不减,于是也就减少CPU资源的消耗了。

打个比方

比如说操作系统就是一个土豪,借东西再换回来不收利息。它有一个很大的内存,周围许多人都想找它去借(新声明的变量并初始化),刚开始借的人只有十几个,后面有上万个,于是它要每天记录谁借了多少内存出去,谁还没有归还,归还的直接从记录上把名字划掉(垃圾回收)。后来操作系统烦了,于是就建了一个很大的内存池,够所有人分批次借,只要借完及时归还就行,而它每次去看这个内存池有没有变小即可,省了不少精力。

而这个内存池就是go buffer pool的作用。

注意

建议给GetPut加锁,防止多个协程同时借阅,造成竞争冒险。
这个CPU占满问题涉及Golang的垃圾回收机制,这块是要点,一定要搞明白。

参考文章和资源

------ 本文结束------
如果本篇文章对你有帮助,可以给作者加个鸡腿~(*^__^*),感谢鼓励与支持!