■河南 刘景云
某单位Hadoop平台最近在运行一个数据量很大的计算任务时,一些计算节点主机出现运行异常故障,无法提供计算服务。
该Hadoop集群由30多个节点组成,可以提供HDFS分布式存储和YARN分布式计算服务。节点主机配置了两颗八核CPU以及64GB的内存,硬件配置是没有问题的,进入Hadoop的日志目录,执行“more yarn-hadoopnodemanager-master.log”命令,查看NodeMagager的日志信息,发现“WARN org.apache.hadoop.yarn.server.nodemanager.containermanager.launcher.Container Launch:Failed to launch container.”“java.lang.OutOfMemoryError:GC overheadlimit exceeded”等警告信息。
这些节点的操作系统为CentOS 7.X,采用的JDK为Oracle JDK 1.8版本,因为出现故障的节点已经无法为外界提供服务,所以Hadoop将其移出分布式计算集群,使其进入黑名单中,执行“jps”命令,发现NodeManager进程消失了,这说明NodeManager服务已经退出,这自然无法为外界提供计算服务。
所有的故障节点都是同样的问题,当出现内存溢出后,NodeManager进程自动关闭,之后被HadoopHadoop放置到黑名单中。
要想处理此类故障,必须深入了解JVM内存分配机制。对于Eden Space来说,当对象被创建时会先被放入到该区域,进行垃圾回收后,无法回收的对象会被放入空的Survivor Sapce。Eden Space和Survivior Space属于新生代,在其中进行垃圾回收的话,性能消耗较小。Old Gen主要用来存放新生代中经过多次垃圾回收依然存活的对象。
当Old Gen中堆满不能收回的对象后,如果默认堆空间使用率达到80%,Major GC会对整个堆进行扫描和回收,其效能是很慢的。非堆内存是Java虚拟机管理堆之外的内存。对于JDK8来说,PermGen Space已被元空间MetaSpace取代,主要用来存放编译器编译后的代码等数据。两者最大的区别在于Metaspace并不存在于虚拟机中,使用的是本地内存。Code Cache主要用来存放JIT所编译的本地代码。
JVM Stack描述的是Java方法执行的内存模型,当Java虚拟机运行程序程序时,每当创建新的线程时,Java虚拟机都会分配一个虚拟机栈。Local Method Stack是为虚拟机使用到Native方法服务。整个堆内存为新生代大小和老年代大小之和,JVM 初始分配的堆内存默认为物理内存的1/64,最大分配的堆内存默认为物理内存的1/4。当默认空闲内存小于40%,JVM 就会增大堆直到最大内存限制。如果默认空闲堆内存大于70%,JVM 就会减小堆直到最小内存限制。
如果最大分配的堆内存过大或过小,就很容易出现“Java.lang.OutOfMemory”的内存溢出错误。当然,对于非堆内存来说,如果最大非堆内存设置过小,也会出现相同的故障,其默认为物理内存的1/4。根据以上分析,JVM 的内存包含堆内存和非堆内存,JVM 的最大内存取决于物理内存和操作系统化的类型,如果在JVM 的参数设置中,初始分配的堆内存大于最大分配的堆内存,或永久代区初始值大于永久代区最大值,那么就会出现问题。如果堆内存最大分配的堆内存和永久代区最大值之和大于物理内存的话,程序是无法启动的。
对于本例中出现的“GC overheadlimit exceeded”错误信息,其实说明了在垃圾回收上存在问题,即执行垃圾回收的时间比例太大,而有效的运算量太小。在默认情况下,如果GC 花费的时间超过98%,且GC 回收的内存小于2%,就会引发该错误。如果该错误不出现,GC 回收的清理的内存就会被再次填满,GC 就会不断的重复执行,造成CPU 占用率过高。要想关闭该提示,可在JVM 参数配置文件中的“JAVA_OPTS=”行添加“-XX:-UseGCOverheadLimit”。
但是,这样配置并不能有效解决内存不足问题,当经过一段时间后,会出现“java.lang.OutOfMemoryError:Java heap space”的警告信息。打开YARN 的“yarnenv.sh”配置文件,在其中查看“YARN_NODEMANAGER_HEAPSIZE”参数,该参数的作用是JVM 运行的堆内存参数的,其默认值为1000MB,可以使用的堆内存为1000MB,但是此处却没有配置。这就说明JVM 使用了默认的堆内存配置信息,但默认的堆内存是比较小的,只能应对数据量较小的计算场景,如果处理巨量的数据的话,就显得不足了。
该问题毫无疑问是因为数据量过大导致的内存溢出。当某个计算节点上的NodeManager 失效,Hadoop 会自动请求下一个NodeManager节点,该节点也会因堆内存不够自动关闭。按照这样的顺序,很可能会导致整个Hadoop集群彻底无法正常运作。
根据以上分析,在默认情况下,每个NodeManager 节点只能得到大约1GB 的堆内存,这应对一般的计算还可以,如果面对巨量的运算场景肯定会出现问题。
解决方法只能给JVM 增加堆内存。在上述“yarn-env.sh”配置文件中添加“export YARN_NODEMANAGER_OPTS="-Xms4096m -Xmx4096m"”行,其中的“-Xms”指的是JVM 的初始分配堆内存值,“-Xmx”参数值的是JVM 的最大分配堆内存值,这里将其均设为4096MB。在所有的NodeManager 节点上都进行这样的配置操作。执行“bin/yarn-daemons.sh start nodemanager”之类的命令,重启NodeManager 服务,让Hadoop 集群恢复正常运作。当然,除了增加最大堆内存,还可以从Job 任务代码等方面进行优化,来有效的避免上述问题。
网络安全和信息化2020年2期