8c0fab045c91c9ab487769c56446eef3
记一次 HDFS NameNode GC 调优

没有碰到过 GC 问题的人生对写 Java 的人来说是不完整的。大数据生态圈的框架大都以 JVM 系语言开发(Java Scala 为主),毕竟生态成熟嘛要啥有啥。

HDFS 作为大数据领域的默认分布式文件系统,其运作方式导致了非常容易碰到 GC 问题:

  • 大量的元数据需要保存在内存中,使得很容易就需要几十 G 甚至 100 多 G 的堆
  • 大量且高并发的文件读写操作使得频繁地产生新对象

下面就举两个例子,简单分享下我们做的一些调优。

案例一

有业务同事反馈任务跑的慢,虽然后来确认是其他原因导致的,但在分析过程中,我们从监控观察到 RPC 排队时间和处理时间不是很稳定,有时会出现几秒甚至10多秒的的毛刺,进而注意到 GC。

一分钟一个点,大概每分钟有 2、3秒花在 GC 上。我们用的是经典的 ParNew + CMS 的组合,查看 GC 日志发现大部分都是新生代的 GC,也就意味着有 3% - 5%的时间是 STW 的。这个比例看着不大,但在 NameNode 每秒几 K 甚至几十 K 的事务的压力下,绝对数值和对具体业务的影响还是不能忽视的。

知道了原因,调整就很简单了。从 NameNode 的工作原理分析,大量文件的读写确实会创建很多临时对象,调大新生代就是很自然也很正确的办法。一方面,更大的新生代能减少 minor gc 的次数;另一方面,更多临时对象在新生代回收也减少了晋升到老年代的对象的数量,减少了 CMS GC 的压力。

但还有个不能忽视的问题,NameNode 的元数据会占据非常大的老年代空间,因此新生代也不能调的太大,否则可能触发频繁的 CMS GC 甚至 Full GC。

具体调多大,很显然不会有标准值,只能根据实际数据总量和 TPS 来综合考虑。好在 NameNode 每个元数据的内存占用是确定的,乘以对象数,再留些余量,就能准确计算出老年代需要的内存。剩下的就可以分给新生代了。如果余量不足,也可以考虑适当调大整个堆的大小。

在我们的生产环境下,80+G 的 heap,我们把新生代设置为 12G,到达了可以接受的效果。


除了 GC 时间变为几分之一外,GC 次数也减少了几乎一个数量级,RPC 排队时间的毛刺也减少到百毫秒级别,恶劣情况下 2 秒左右,CMS GC 的次数也减少到 10 多个小时 1 次。

案例二

top Created with Sketch.