03 July 2015

IMG-THUMBNAIL

结合我遇到的实际情况说一下大Golang的垃圾回收,垃圾回收太深奥,这方面相关的我都是搬运来的

我们公司服务端都是在用Golang,每天几百万的UV,过亿的PV一直没啥问题。后来改了一次逻辑,首页能展示一个列表,之前这个列表都没有做过缓存处理,一个是因为数据少,一共才一千多条;还有就是量不大。但是首页加了入口之后出了问题,内存疯涨,一般内存也就占200M+,这还是包含In Memory缓存的情况下,这下好家伙,最高我见过占用20G内存的情况,但是程序重启一下又能好。当然这是后知后觉了,当时也没把这两个东西联系到一起。

完整的说一下事情的经过。

上线之后内存疯涨,一开始会占10G左右,不知道为啥,只能kill掉。程序重启之后短时间内也会维持在200M左右,但是过个4、5天又会涨上来,而这个过程中内存大小都没有影响用户使用。最后只能跟运维说,看见内存大了就kill一次。。。

当时觉得内存这么大,肯定是GC的问题,不晓得是哪里内存泄漏了。我们用的是beego,我就用它封装好的内存pprof和CPU检测工具各种查,实在是没发现啥可疑的。毫无头绪。

后来机缘巧合去看了Gopher China 2015大会的视频,全程视频都放在了慕课网,大家也可以去看看。都是业内用Golang的大佬的演讲。推荐大家去看:

  1. Go 语言在游戏项目的应用情况汇报,达达在GC方面在国内算是先驱了,很多人研究GC都是参考的他的数据。
  2. Go 语言构建高并发分布式系统实践,Golang在360的应用。
  3. 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的优化方式原则就是尽可能少的声明临时变量:

  1. 局部变量尽量复用;
  2. 如果局部变量过多,可以把这些变量放到一个大结构体里面,这样扫描的时候可以只扫描一个变量,回收掉它包含的很多内存;

Golang目前一直在优化GC,目前整体效果来看和大Java差不多,但是稳定性上面来看,还是不行。一般压力测试第一波上面效果极差。


参考文献
  1. Go 的垃圾回收机制在实践中有哪些需要注意的地方? - 达达

原文链接:Golang的垃圾回收,转载请注明来源!

EOF