2020-10-15
< view all posts在特征工程的MR任务中,目前遇到两种不同的OOM报错,处理方法也不同。
第一种OOM,报错信息如下:
WARN [main] org.apache.hadoop.mapred.YarnChild: Exception running child : org.apache.hadoop.mapreduce.task.reduce.Shuffle$ShuffleError: error in shuffle in fetcher#10 at org.apache.hadoop.mapreduce.task.reduce.Shuffle.run(Shuffle.java:134) at org.apache.hadoop.mapred.ReduceTask.run(ReduceTask.java:376) at org.apache.hadoop.mapred.YarnChild$2.run(YarnChild.java:168) at java.security.AccessController.doPrivileged(Native Method) at javax.security.auth.Subject.doAs(Subject.java:415) at org.apache.hadoop.security.UserGroupInformation.doAs(UserGroupInformation.java:1614) at org.apache.hadoop.mapred.YarnChild.main(YarnChild.java:163) Caused by: java.lang.OutOfMemoryError: Java heap space
报错信息显示在reduce的shuffle阶段,Java的堆空间不足,那么能想到比较直接的方法是增加分配给每个reducer的内存空间,但是实际上这样不起作用。
通过进一步查找官方论坛的issue,发现这是Hadoop的一个bug导致的。
这个bug的起因是Hadoop对两个参数的默认值配置不正确,分别是:mapreduce.reduce.shuffle.input.buffer.percent 以及 mapreduce.reduce.shuffle.memory.limit.percent。前一个参数规定了shuffle阶段的总数据能够占用内存的多大比例,而后一个参数规定了每个shuffle任务能够使用这些内存的比例,超出这个空间的数据就不保存在内存,而是会暂存在硬盘上。
这两个参数在Hadoop中的默认值是0.9和0.25,而并行的Fetcher线程数默认是5个,那么我们可以计算出,极限情况下shuffle会被分配到 0.9*0.25*5=1.125 比例的内存,已经超出了100%,所以会导致内存溢出。
定位到问题之后,解决的方法就是降低分配给 shuffle 的内存的比例,让计算过程中数据更多地存到硬盘而非内存上。目前使用如下的值可以避免这个问题:
mapreduce.reduce.shuffle.input.buffer.percent = 0.4
mapreduce.reduce.shuffle.memory.limit.percent = 0.15
第二种OOM,报错信息如下:
INFO [communication thread] org.apache.hadoop.mapred.Task: Communication exception: java.lang.OutOfMemoryError: Java heap space at sun.nio.cs.UTF_8.newDecoder(UTF_8.java:68) at java.lang.StringCoding$StringDecoder.<init>(StringCoding.java:131) at java.lang.StringCoding$StringDecoder.<init>(StringCoding.java:122) at java.lang.StringCoding.decode(StringCoding.java:187) at java.lang.String.<init>(String.java:416) at java.lang.String.<init>(String.java:481) at java.io.UnixFileSystem.list(Native Method) at java.io.File.list(File.java:1116) at org.apache.hadoop.yarn.util.ProcfsBasedProcessTree.getProcessList(ProcfsBasedProcessTree.java:459) at org.apache.hadoop.yarn.util.ProcfsBasedProcessTree.updateProcessTree(ProcfsBasedProcessTree.java:201) at org.apache.hadoop.mapred.Task.updateResourceCounters(Task.java:845) at org.apache.hadoop.mapred.Task.updateCounters(Task.java:984) at org.apache.hadoop.mapred.Task.access$500(Task.java:78) at org.apache.hadoop.mapred.Task$TaskReporter.run(Task.java:733) at java.lang.Thread.run(Thread.java:745)
也是在reduce阶段出现了Java堆空间不足的情况,但是具体原因和上面不同。
通过报错信息可以看到,是yarn的内存调度时,创建用来保存线程树的字符串时出现问题。
进一步分析具体原因,虽然我们已经通过配置 mapreduce.reduce.memory.mb ,给每个reduce分配了比较大的内存,但是这些内存大部分是用来缓存reduce使用和生成的数据,而启动reducer所在的JVM虚拟机时使用的内存则是由另一个参数,mapreduce.reduce.java.opts 来定义的。这个参数的默认值并不大(200m),因此会造成JVM的资源不足。
将 mapreduce.reduce.java.opts 配置为2048m即可解决问题。需要注意的是 mapreduce.reduce.java.opts(给reducer的JVM的内存) 必须要小于 mapreduce.reduce.memory.mb(一个reducer能用的总内存)。