MapReduce任务两种OOM问题的排查处理

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能用的总内存)。