10

内存占用过高

 3 years ago
source link: https://www.zenlife.tk/huge-memory.md
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

内存占用过高

2017-02-18

就从这个issue说起吧。它描述的问题是这样子的,事务在提交之前,会把所有操作的数据都在本地组织好,然后一口气提交。这部分数据显然是需要占用内存的。在发往kv层之前,通讯需要加一次编码,将数据marshal成protobuf格式,这又是一份内存,相当于内存的开销再乘2。

作为一个优化,充分利用了Go语言在并发上的优势,当然是由许许多多的goroutine在并发地写的,网络IO自然不会那么快,所以大部分goroutine阻塞在发送数据上面排队。于是它们引用的内存是暂时无法被GC掉的,也就是说这些内存都是实际要占用一段时间的。

如果遇到事务冲突被abort掉之类的,就更悲剧了。尤其是大事务,白白占资源那么久,最后还没成功。当然这块的代码也是比较麻烦的,涉及到失败后,goroutine的各种资源释放的问题,也是比较容易出现leak的地方。死掉还好,最怕session搞了一个超大的事务完不成,却还活着的结束不掉,又不释放资源,那整个系统都死了,这种case线也出现过。扯远了,继续说内存占用。

记录结构的表示问题,一张表或者一个数据库在文件里面存下来是1G,加载进内存后是不是1G呢?显然不是。到内存里面表示的结构要复杂得多,将这些数据组织起来,花掉的内存比原始数据大许多,多好几倍也不稀奇。只是大部分时候是不需要把大量数据load进来的,但做join或者一些复杂的查询,全表扫或者delete,就不定了。

就说说order by。如果我想select一些数据并order by,数据是分布在多个结点的,当然可以多goroutine并行的读取,但是要求返回有序,就需要全部缓存在内存里重排序。如果可以流式地处理,可能占不到多少内存的,一旦加了order by,可能占的内存就翻了许多倍。

再说GC对内存占用的影响。Go语言的GC机制可以把它的内存占用看成三个层次:

  1. heap_in_use是程序实际需要用到的内存的量,这部分是被引用到的,即使GC之后也无法释放。我们真正需要关注的是这项。
  2. GC管理的内存,GC管理的内存达到上次实际使用内存量的两倍,会触发GC操作,比如当前heap_in_use是512M,那么当内存涨到1G的时候触发GC,经过回收之后的内存也许是300M,那么再次触发的时机就是内存使用量涨到600M的时候。
  3. 内存分配器管理的部分。向操作系统按页级别地申请大块内存,再给GC的分配使用。当GC回收之后,只是归还到分配池里面,还不会立刻归还给操作系统。

经过这个GC机制的影响,平时程序占用的内存是实际需要的内存2倍以上。如果刚执行完一个很耗资源的操作,由top看到的内存跟实际相差许多倍,这也是正常的,分配器还没把内存归还给系统而已。

做存储肯定都知道写放大的概念,其实内存里面也同样类似。需要操作的数据看起来不多,而实际占用的内存却是好多倍。这个数据具体是多少呢?

根据这边的一些观察,实际上处理的数据就90M~100M,而程序的内存占用达到了900M~1G !!! 还真别惊讶,就按指数倍翻了几次而已。

说了很多对内存占用放大的因素,其实我可以确切地知道哪些地方占内存,为什么占内存,但是说到解决,却也只能两手一摊。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK