7

Oozie任务死锁解决方案

 3 years ago
source link: https://niyanchun.com/oozie-deadlock-solution.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Oozie任务死锁解决方案

Oozie是Apache下面的一个用于流程调度(workflow scheduler)的系统,主要用于管理Hadoop生态圈中的各种任务,目前支持丰富的任务类型:Java MR、Streaming MR、Pig、Hive、Sqoop、Spark、Shell等。如果想详细了解Ooize强大的调度功能,可参考其官方(http://oozie.apache.org/)文档。本文主要讨论使用Oozie来调度任务时可能出现的死锁问题。

严格来说并不是Ooize导致的死锁,而是YARN的调度机制导致的死锁。我们先来解释一下何时会产生死锁,以及原因。之前在《Hadoop系列六——YARN调度策略》一文中我们已经了解到YARN目前主要有三种调度策略,而最常使用的是Capacity Scheduler和Fair Scheduler,这两种策略都是基于队列的,而且默认只有一个default队列,也就是所有的任务都是放在这个队列中的;同一队列内使用FIFO、Fair、DRF三者之一。而产生死锁的原因和我们进程死锁道理其实是一样的,YARN支持一个任务(在YARN里面一般叫Application,Oozie里面叫Job,本文统一用任务来指代这两个概念)里面可以产生新的子任务,这样父任务会一直等待所有子任务完成后自己才会完成退出。这样如果某一时刻提交了很多任务,这些任务也会产生若干子任务,而资源是有限的,如果这些父任务占光了所有资源,那产生的子任务就只能一直等待,无法运行。而父任务却一直在等待子任务的返回,这样便产生了死锁。

可见,产生死锁的必要条件就是任务会产生子任务,而Ooize的机制恰好是这样的:Oozie拉起一个YARN应用的机制是先拉起一个MapReduce任务(称为oozie launcher任务),然后该MR任务拉起真正的任务(文章刚开始提到的那些任务)。举个死锁的例子:某一时刻我们通过Oozie提交了n个Spark任务(通过Oozie的Spark Action或Shell Action),这样Oozie会向YARN提交n个MapReduce任务(oozie launcher),假设m(m≤n)个MR任务获得了资源并且创建了spark任务,但此时队列内的资源都被这m个MR任务占用了,所以spark任务一直在等待资源,而那m个MR任务却在等待spark任务完成返回,这样便产生了死锁。

目前我还没有发现有比较完美的方案可以完全杜绝这种死锁的情况,但通过一些手段可以极大的避免死锁:

  • 多任务队列。可以看到死锁主要是因为任务抢间占同一资源导致,所以我们可以通过划分多个队列,将父子任务分到不同的队列里面去,各自使用各自队列的资源。比如我们可以专门划分一个队列(假设队列名为root.oozie_launcher)用于放oozie launcher任务,Oozie提供了一个oozie.launcher.mapred.job.queue.name这个配置来设置oozie launcher任务要放置的队列名,目前没有全局配置,只能在每一个workflow.xml里面去配置该选项。当然,单单这样做还是不能比较好的解决这个问题,因为这样只是解决了父子任务竞争同一资源的问题,子任务之间的竞争还没有解决。比如父任务特别多,拉起了非常多的子任务,这些子任务之间因为相互抢占资源,导致都不能返回,那父任务也就会一直等待下去。但我们不可能为每个子任务分配一个单独的队列,而且也无法预估每个子任务到底需要多少资源。这个时候我们就需要另外一种辅助手段了。
  • 并发任务数限制。通过多任务队列的方式我们避免了父子任务的竞争,通过限制队列内并发任务数来限制同一队列内任务的竞争。限制的方式有很多种,在《Hadoop系列六——YARN调度策略》一文中我们已经提到了很多配置项,这里以Fair Scheduler为例(Capacity Scheduler有对应的配置)列几个比较常用且有效的:

    • maxRunningApps:这个配置是最直观的,限制队列内可以同时运行的任务个数。
    • maxAMShare:这个配置比较隐晦一点,用于限制队列内有多少比例的资源可以用来创建AM(Application Master),默认值为0.5,-1表示不检查AM占用的资源,实质就是不限制。为什么这个也可以限制并发数呢?因为每个YARN应用启动时第一件事情就是申请资源创建AM,我们限制了这个值,就相当于限制了AM的个数,从而也就限制了任务的个数。
    • maxResources:这个配置也比较隐晦,需要我们比较了解YARN的调度机制。YARN的elastic queue特性使得队列之间可以相互抢占资源,所以我们的多任务队列方式并不能完全隔离父子任务的竞争(或者说队列之间的竞争),而该配置限制了某个队列资源的上限(自身分配的资源+从别的空闲队列抢占的资源)。举个例子,比如A队列(运行父任务)的资源配额为a,B队列(运行子任务)的资源配额为b,如果某一时刻父任务特别多,而子任务还没有创建或者运行,即子任务队列B资源是空闲的,那父任务就会从B队列中抢占资源,等到子任务后面再去申请的时候,已经被抢走了,而默认子任务只有在自己被抢占的资源释放后,才会获得,所以这样也就产生了死锁。所以我们可以通过该选项设置每个队列资源上限,保证任何情况下都不要把别的队列的资源全部抢占(但为了提高资源使用率,要适当的允许抢占)。同时,为了避免最坏的情况,我们最好也要开启YARN的Preemption特性(开启及配置方法可参见我之前的文章),保证极端情况下,本队列的任务可以强制拿回属于自己队列的资源。

当然,通过上述两种手段只能降低风险,但无法完全杜绝(除非我们不考虑系统资源的利用率,每个队列同一时刻只允许一个任务运行)。举个极端例子,比如我们有两个队列:父任务队列和子任务队列。某一时刻同时上来了两个父任务,并且他们同时创建了两个子任务,这两个子任务开始的时候只需要少量资源(比如MR任务是边运行边根据情况申请资源的),所以他们都在子任务队列运行起来了,但随着不断运行,一直申请资源,某个时刻资源不够用了(不管是自己队列的资源,还是抢占别的队列之后的),那这两个子任务就只能等待了,这样就又产生了死锁了。不过,从系统稳定性角度来说,一般我们要保证系统的(平均)负载低于某个阈值,典型的比如80%或50%(根据具体场景不同),而不是一味的追求太高的资源使用率(比如之前参加阿里菜鸟网络的一个技术分享会的时候,他们说他们的云平台如果检测到CPU使用率超过50%就会预警。其依据是现在的CPU都是一个物理核再虚拟一个核出来)。所以我认为对于YARN中的资源使用也一样,资源使用一直很高并非一件好事,我觉得资源平均使用率能超过50%对许多系统来说已经是一件非常不错的事情了。

上述的这些方案也只是个人的一些观点和解决方案,如果你有更好的避免死锁的方案,欢迎讨论指正。


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK