最近开发的一个 HQL 脚本,扔到集群上跑的时候报 shuffle 阶段出现 OOM 的错误,这个问题之前没有遇到过;于是上网搜了下,发现网友也遇到过类似的问题,详细阅读后再结合 MapReduce 任务的 shuffle 原理,将问题解决的过程记录下来,方便以后查阅。

0x00 报错信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
- Task with the most failures(4): 
- -----
- Task ID:
- task_1548652929194_403817_r_000214
-
- URL:
- http://0.0.0.0:8088/taskdetails.jsp?jobid=job_1548652929194_403817&tipid=task_1548652929194_403817_r_000214
- -----
- Diagnostic Messages for this Task:
- Error: org.apache.hadoop.mapreduce.task.reduce.Shuffle$ShuffleError: error in shuffle in fetcher#19
- 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:164)
- at java.security.AccessController.doPrivileged(Native Method)
- at javax.security.auth.Subject.doAs(Subject.java:422)
- at org.apache.hadoop.security.UserGroupInformation.doAs(UserGroupInformation.java:1727)
- at org.apache.hadoop.mapred.YarnChild.main(YarnChild.java:158)
- Caused by: java.lang.OutOfMemoryError: Java heap space
- at org.apache.hadoop.io.BoundedByteArrayOutputStream.<init>(BoundedByteArrayOutputStream.java:56)
- at org.apache.hadoop.io.BoundedByteArrayOutputStream.<init>(BoundedByteArrayOutputStream.java:46)
- at org.apache.hadoop.mapreduce.task.reduce.InMemoryMapOutput.<init>(InMemoryMapOutput.java:63)
- at org.apache.hadoop.mapreduce.task.reduce.MergeManagerImpl.unconditionalReserve(MergeManagerImpl.java:305)
- at org.apache.hadoop.mapreduce.task.reduce.MergeManagerImpl.reserve(MergeManagerImpl.java:295)
- at org.apache.hadoop.mapreduce.task.reduce.Fetcher.copyMapOutput(Fetcher.java:514)
- at org.apache.hadoop.mapreduce.task.reduce.Fetcher.copyFromHost(Fetcher.java:336)
- at org.apache.hadoop.mapreduce.task.reduce.Fetcher.run(Fetcher.java:193)

0x01 原因分析

(1) 文件拷贝:默认情况下,当整个 MapReduce 作业的所有已执行完成的 Map Task 任务数超过 Map Task 总数的 mapreduce.job.reduce.slowstart.completedmaps (默认为0.05) 后,ApplicationMaster 便会开始调度执行 Reduce Task 任务。然后 Reduce Task 任务默认启动 mapreduce.reduce.shuffle.parallelcopies (默认为5) 个 MapOutputCopier 线程到已完成的 Map Task 任务节点上分别copy一份属于自己的数据。 这些copy的数据会首先保存的内存缓冲区中,当内冲缓冲区的使用率达到一定阀值后,则写到磁盘上。

(2) 内存缓冲区:这个内存缓冲区大小的控制就不像map那样可以通过 mapreduce.task.io.sort.mb 来设定了,而是通过另外一个参数来设置:mapreduce.reduce.shuffle.input.buffer.percent(default 0.7), 这个参数其实是一个百分比,意思是说,shuffile在reduce内存中的数据最多使用内存量为:0.7 × maxHeap of reduce task。

(3) 原则上,mapreduce.reduce.shuffle.input.buffer.percent * mapreduce.reduce.shuffle.parallelcopies 必须小于等于1,否则就会出现如上错误。但是,这两个参数默认值的乘积为3.5,远远超过了1,为什么没有经常抛出以上的错误呢?

  • 首先,把默认值设为比较大,主要是基于性能考虑,将它们设为比较大,可以大大加快从map复制数据的速度;
  • 其次,要抛出如上异常,还需满足另外一个条件,就是map任务的数据一下子准备好了等待shuffle去复制,在这种情况下,就会导致shuffle过程的“线程数量”和“内存buffer使用量”都是满负荷的值,自然就造成了内存不足的错误;而如果map任务的数据是断断续续完成的,那么没有一个时刻shuffle过程的“线程数量”和“内存buffer使用量”是满负荷值的,自然也就不会抛出如上错误。

0x02 调优方式

通过上述的原因分析,调优方式如下:

  • 将 mapreduce.reduce.shuffle.input.buffer.percent 参数调小,辅助参数 mapreduce.reduce.shuffle.parallelcopies;
  • 将 mapreduce.reduce.java.opts 参数调大,辅助参数 mapreduce.reduce.memory.mb;

参考文献

HQL内存溢出的参数调优