分享

Hadoop作业JVM堆优化汇总及JVM复用

本帖最后由 pig2 于 2014-4-27 20:59 编辑
问题导读:
1.mapred.child.java.opts是用来做什么的?
2.如何让mapred.child.java.opts的值不能随便修改?
3.通过什么参数可以配置作业的Map和Reduce阶段的heap的大小?
4.mapreduce.admin.map.child.java.opts和mapreduce.admin.reduce.child.java.opts的作用是什么?
5.JVM复用的目的是什么?





前段时间,公司Hadoop集群整体的负载很高,查了一下原因,发现原来是客户端那边在每一个作业上擅自配置了很大的堆空间,从而导致集群负载很高。下面我就来讲讲怎么来现在客户端那边的JVM堆大小的设置。

我们知道,在mapred-site.xml配置文件里面有个mapred.child.java.opts配置,专门来配置一些诸如堆、垃圾回收之类的。看下下面的配置:

  1. <property>
  2.    <name>mapred.child.java.opts</name>
  3.    <value>-Xmx200m</value>
  4.    <description>Java opts for the task tracker child processes.
  5.    The following symbol, if present, will be interpolated: @taskid@ is
  6.    replaced by current TaskID. Any other occurrences of '@' will go unchanged.
  7.    For example, to enable verbose gc logging to a file named for the taskid in
  8.    /tmp and to set the heap maximum to be a gigabyte, pass a 'value' of:
  9.          -Xmx1024m -verbose:gc -Xloggc:/tmp/@taskid@.gc
  10.    The configuration variable mapred.child.ulimit can be used to control the
  11.    maximum virtual memory of the child processes.
  12.    </description>
  13. </property>
复制代码

默认情况下,-Xmx都是配置200m的,但是在实际情况下,这个显然是不够用的,所以导致客户端那边会设置更大的值。那怎么来限制用户随便设置Xmx的值呢?有下面两种方法:

一、可以自己定义一个变量,比如如下:

  1. <property>
  2.    <name>mapred.task.java.opts</name>
  3.    <value>-Xmx2000m</value>
  4. </property>
  5. <property>
  6.    <name>mapred.child.java.opts</name>
  7.    <value>${mapred.task.java.opts} -Xmx1000m</value>
  8.    <final>true</final>
  9. </property>
复制代码

上面的mapred.task.java.opts属性是我们自己定义的,可以公布给用户配置;然后在mapred.child.java.opts中获取到mapred.task.java.opts的值,同时mapred.child.java.opts属性的final被设置为true,也就是不让客户修改。所以用户对mapred.child.java.opts直接配置是无效的;而且这里我们在获取${mapred.task.java.opts}之后再添加了-Xmx1000m,而在Java中,如果相同的jvm arg写在一起,比如”-Xmx2000m -Xmx1000m”,后面的会覆盖前面的,也就是说最终“-Xmx1000m”才会生效,通过这种方式,我们就可以有限度的控制客户端那边的heap size了。同样的道理,其他想覆盖的参数我们也可以写到后面。


我们可以通过
  1. <property>
  2.   <name>mapred.map.child.java.opts</name>
  3.   <value>
  4.      -Xmx512M
  5.   </value>
  6. </property>
  7. <property>
  8.   <name>mapred.reduce.child.java.opts</name>
  9.   <value>
  10.      -Xmx1024M
  11.   </value>
  12. </property>
复制代码
来分别配置作业的Map和Reduce阶段的heap的大小。


二、通过mapreduce.admin.map.child.java.opts和和mapreduce.admin.reduce.child.java.opts设定
上述限制客户端那边随便设置堆大小是通过重新定义一个变量给用户使用,这样用户得使用新的变量来定义一些JVM相关的设定,如果用户那边的脚本非常多,他们就需要一个一个脚本的修改mapred.child.java.opts为mapred.task.java.opts。这样会很不方便。

这里介绍另外一种方法。可以通过mapreduce.admin.map.child.java.opts和mapreduce.admin.reduce.child.java.opts来限定作业map和reduce的堆的大小。他们都是管理员设定的map/reduce阶段申请的container的默认JVM启动参数。启动container的命令行会先连接管理员设定参数,然后再连接用户设定参数。我们来看看Hadoop源码是怎么获取客户端和管理员JVM参数的获取的:

  1. private static String getChildJavaOpts(JobConf jobConf, boolean isMapTask) {
  2.     String userClasspath = "";
  3.     String adminClasspath = "";
  4.     if (isMapTask) {
  5.       userClasspath =
  6.           jobConf.get(
  7.               JobConf.MAPRED_MAP_TASK_JAVA_OPTS,
  8.               jobConf.get(
  9.                   JobConf.MAPRED_TASK_JAVA_OPTS,
  10.                   JobConf.DEFAULT_MAPRED_TASK_JAVA_OPTS)
  11.           );
  12.       adminClasspath =
  13.           jobConf.get(
  14.               MRJobConfig.MAPRED_MAP_ADMIN_JAVA_OPTS,
  15.               MRJobConfig.DEFAULT_MAPRED_ADMIN_JAVA_OPTS);
  16.     } else {
  17.       userClasspath =
  18.           jobConf.get(
  19.               JobConf.MAPRED_REDUCE_TASK_JAVA_OPTS,
  20.               jobConf.get(
  21.                   JobConf.MAPRED_TASK_JAVA_OPTS,
  22.                   JobConf.DEFAULT_MAPRED_TASK_JAVA_OPTS)
  23.               );
  24.       adminClasspath =
  25.           jobConf.get(
  26.               MRJobConfig.MAPRED_REDUCE_ADMIN_JAVA_OPTS,
  27.               MRJobConfig.DEFAULT_MAPRED_ADMIN_JAVA_OPTS);
  28.     }
  29.    
  30.     // Add admin classpath first so it can be overridden by user.
  31.     return adminClasspath + " " + userClasspath;
  32. }
复制代码

通过上面的代码,我们可以发现Hadoop是先获取管理员的JVM参数配置,然后连接客户端那边JVM参数的配置。这样如果管理员那边的配置在客户端那边也配置了,那么客户端这边的配置将会覆盖掉管理员那边的参数配置。所以我们可以修改源码,将 return adminClasspath + ” ” + userClasspath;修改为 return userClasspath + ” ” + adminClasspath;然后在mapred-site.xml文件做如下配置:
  1. <property>
  2.       <name>mapreduce.admin.map.child.java.opts</name>
  3.       <value>-Xmx1000m</value>
  4. </property>
  5. <property>
  6.       <name>mapreduce.admin.reduce.child.java.opts</name>
  7.       <value>-Xmx1000m</value>
  8. </property>
复制代码

这样,我们就可以覆盖客户端那边的配置。

总结
上面两种方法虽然能在一定程度上限制客户端使用堆的大小,但是这样的解决办法不是很好的!因为我们设定所有作业的堆大小都是1000M,但是实际情况下,很多作业不一定都用得到1000M;而且在一些情况下,有些作业用到的heap可能大于1000M,这样会使这样的作业出现OOM的问题。





这里找到了另外一篇做一下对比:


前一阵子发现用户提交的hive query和hadoop job会导致集群的load非常高,经查看配置,发现很多用户擅自将mapred.child.java.opts设置的非常大,比如-Xmx4096m(我们默认设置是-Xmx1024m),  导致了tasktracker上内存资源耗尽,进而开始不断swap磁盘上数据,load飙升。

TaskTracker在spawn一个map/reduce task jvm的时候,会根据用户JobConf里面的值设定jvm的参数,然后写入一个taskjvm.sh文件中,然后调用linux命令"bin/bash -c taskjvm.sh"来执行task,mapred.child.java.opts就是设定jvm的参数之一,在新版本中已经标注Deprecateded,取而代之的是区分Map task和Reduce task的jvm opts,mapred.map.child.java.opts和mapred.reduce.child.java.opts(默认值为-Xmx200m)


当用户在不设该值情况下,会以最大1G jvm heap size启动task,有可能导致OutOfMemory,所以最简单的做法就是设大参数,并且由于这个值不是final,所以用户在自己的mapred-site.xml中可以覆盖默认值。但是如果很多用户都无限度设置的话,high load问题就来了。

其实在构造JVM Args的过程中,是有另外一个admin参数可以覆盖用户端设置的mapreduce.admin.map.child.java.opts, mapreduce.admin.reduce.child.java.opts
经测试,如果相同的jvm arg如果写在后面,比如"-Xmx4000m -Xmx1000m",后面的会覆盖前面的,“-Xmx1000m”会最终生效,通过这种方式,我们就可以有限度的控制heap size了

最终在mapred-site.xml中加上

  1. <property>  
  2.       <name>mapreduce.admin.map.child.java.opts</name>  
  3.       <value>-Xmx1024m</value>  
  4. </property>  
  5. <property>  
  6.       <name>mapreduce.admin.reduce.child.java.opts</name>  
  7.       <value>-Xmx1536m</value>  
  8. </property>  
复制代码


构造child java opts的call stack:
20130611220558078.png

不过这种方式只是限定了task的jvm heap最大限制,如果用户hive query优化不够好还是会抛出OOM,其实是把问题抛给了用户,
接下来还要和用户一起看下到底是哪些query会占用如此大memory,看看有没有进一步优化的空间




JVM还可以复用,这样对于小文件,可以避免资源浪费

Hadoop默认为每个task(map task 或者 reduce task) 启动一个jvm。
鉴于目前小文件过多的问题,设置了jvm复用,即一个job内,多个task共享jvm,避免多次启动jvm,浪费资源和时间。
测试Job信息:
map:4715个
reduce:20个
input:  34G
output:  25G
优化前:1464 s
优化后:1375 s
Job运行时间减少 6%
CPU使用率情况:
*注意: mapred.job.reuse.jvm.num.tasks这个参数是客户端参数,修改不需要重启tasktracker,可以在提交job的shell或者代码中设置。
jvm.png






本帖被以下淘专辑推荐:

已有(7)人评论

跳转到指定楼层
howtodown 发表于 2014-4-28 17:29:30
上面没有说明如何重用JVM,这里补充一下:
可以通过任务Java虚拟机(JVM)重用来解决这个问题,默认每JVM只运行一个任务,使用JVM重用后一个JVM可以顺序执行多个任务,减少了启动时间。控制JVM的属性是mapred.job.reuse.jvm.num.tasks,它指定作业每个JVM运行的任务的最大数量,默认为1。可以通过JonConf的setNumTasksToExecutePerJvm()方法设置,若设置为-1则说明统一作业中共享一个JVM任务的数量不受限制。
回复

使用道具 举报

perfri 发表于 2014-4-29 09:49:54
赞一个,支持一下
回复

使用道具 举报

zxy67648875 发表于 2014-5-15 13:29:58
好 帖子 mark 一下
回复

使用道具 举报

hua0704 发表于 2015-3-29 00:13:17
回复

使用道具 举报

pig2 发表于 2015-4-1 20:04:52
daizj  与本站网友私信公布出来
  1. 请教一下,我跑一下hive查询,报如下内存溢出
  2. hive> select * from t_ba_cf_olap_base_info where ds=20150322 limit 1;
  3. OK
  4. SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
  5. SLF4J: Defaulting to no-operation (NOP) logger implementation
  6. SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
  7. Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
  8. 只需要配置mapred.child.java.opts参数就行吗?
复制代码
me:每个错误都有自己的环境,可以尝试文章的方法


daizj  
  1. <property>
  2.     <name>mapred.child.java.opts</name>
  3.     <value>-Xmx4096m</value>
  4. </property>
  5. <property>
  6.     <name>mapred.map.child.java.opts</name>
  7.     <value>-Xmx4096m</value>
  8. </property>
  9. <property>
  10.     <name>mapred.reduce.child.java.opts</name>
  11.     <value>-Xmx4096m</value>
  12. </property>
  13. 谢谢。我加上了这三个配置都没有效果。
复制代码

me:是否生效,多进行尝试,如还不能解决。可发帖,把问题描述清楚






daizj  


谢谢,问题已解决,是hive客户端配置的堆内存默认只有256M太小,调大后就正常了




























回复

使用道具 举报

chief 发表于 2015-6-25 11:54:33
多谢楼主了
回复

使用道具 举报

fullmooner 发表于 2016-7-15 10:21:05
分析的特别深入,学习了
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

推荐上一条 /2 下一条