一、问题描述
今天有业务反馈有个MapReduce任务运行很慢,于是看了下JobHIstory上任务的运行情况,发现任务就剩一个reduce还在执行,当时第一反应以为是出现了数据倾斜。但实际排查后发现不是,因为这个任务的reduce task平均执行时间就是2个小时那里(有几十个reduce task),还在运行的那个reduce task是因为出现了失败重试,所以过了一个多小时也一直没跑完:
二、问题分析
看了JobHIstory的UI,发现这个任务失败了两次,这两次失败的情况都不一样。下面我详细的分析一下这两个错误的产生原因
1、第一次失败(yarn的磁盘健康检查机制导致的任务失败)
其实JobHIstory的异常信息说的很明白了,是因为节点不可用,所以taskAttempt被kill。那为什么节点会变成不可用呢?后面排查发现,在那个时间点机器的某几块磁盘达到90%+,随即就想到了yarn的磁盘健康检查机制。
但是我们的节点有10块磁盘,node不健康的阈值配的是25%,也就是说只要保证有超过25%的磁盘是健康的,这个节点就认为是健康的(挂了8块磁盘才会导致node节点被认为是不健康的)。
后面在网上搜一些资料,才发现yarn磁盘健康检查机制的一个细节。就是yarn磁盘健康检查线程会检查两种类型的磁盘:local-dirs和log-dirs。
- local-dirs是通过
yarn.nodemanager.local-dirs
配置的,一般是用来存放yarn任务的一些临时文件的。 - log-dirs是通过
yarn.nodemanager.log-dirs
配置的,一般是用来存放任务运行时输出的相关日志的。
我们集群的local-dirs配置了10块磁盘,log-dirs却只有一块磁盘,因此刚好log-dirs的那块磁盘使用率达到了90%,所以该磁盘被认为是不健康的,继而yarn磁盘健康检查机制则判定该Node是不健康的。通过NodeManager的日志,我们也可以看出确实是这么一回事:
从日志1/1 log-dirs are bad
可以看出, 所有的log-dirs都变成了不健康的,随即Node马上变成不健康的了。
yarn的Node变成不健康后,会kill该Node上所有的Container,因此任务的task失败。
2、第二次失败(map和reduce资源竞争导致的死锁)
在第一次因为磁盘健康的问题失败后,我们发现该reduce task的第二次运行也失败了。看错误描述也很简单,网上搜了下也发现很多该问题的解决方案。
这个问题主要是因为reduce提前启动导致的。在MapReduce的流程中,reduce启动后需要先copy map的相关shuffle数据,然后全部搜集完所有map的数据后才能处理(具体流程看mapreduce shuffle的细节,这里不详细展开)。hadoop为了加快MapReduce job的整体速度,会提前启动一些reduce,先去拉取那些已经完成的map的数据,这样就可以节省后面的一部分IO时间。但是这样设计就会有一个问题,就是当资源有限的情况下,reduce占用了map的资源,然后又在等待map的数据,map则在等待资源运行,这就形成了一个死锁。之后在发生死锁一定时间后,hadoop就会kill掉那个reduce task。
这种情况我们可以通过调整参数来推迟reduce task的执行:
- mapreduce.job.reduce.slowstart.completedmaps:默认是0.05,表示只要有5%的map task执行完,就可以开启reduce task开始copy数据了。我们调大这个值推迟reduce task的执行
三、总结
这次主要的原因还是因为刚好日志所在磁盘使用率达到了90%,导致整个节点不可用。虽然说是当时刚好有几个大任务写了很多数据到磁盘,导致磁盘使用率飚高。但是这也说明集群的健壮性还不够。
目前集群各个机器磁盘的平均使用率大概在74%,这其实不是一个比较安全的值,稍微几个大任务可能就会导致类似的情况再次发生。因此,后续需要对集群一些无用或者冷数据进行删除或者压缩(在无法扩容的情况下)来释放磁盘空间,提高集群的健壮性。