正确理解Yarn容量调度中的capacity参数

1,372 阅读6分钟

我正在参与掘金创作者训练营第4期

【初步结论】

容量调度器中,配得最多的应该就是capacitymaximum-capacity了,一个是当前队列的资源容量,一个是队列可使用的最大容量。多个队列的容量之和为100。

maximum-capacity这个参数还好理解,即队列可使用资源的上限。

假如有多个队列,每个队列都将maximum-capacity的值设置成与capacity一样,意味着每个队列只能使用固定大小的资源,不能超额使用其他队列空闲资源,这样,也就可能出现资源浪费或利用率低的情况。

因此,通常该值会设置成比capacity大。例如都设置为100,也就是每个队列最大都可以使用集群的全部资源。

但既然最大都可以使用集群的全部资源,那么capacity参数的作用和意义到底是什么,该参数又是如何限制用户资源使用的。

查看了官方文档,网上也看了不少文章,始终觉得没有讲透capacity这个参数的意义,索性直接撸源码。

结合源码,并对照日志,确认了几个关键点后,对自己的结论很是自信,立马邮件同步给组内的小伙伴。

队列的capacity参数是单个用户在该队列中所能使用资源的上限。

由于允许多个不同的用户向同一个队列提交任务,因此多个用户的不同任务的资源叠加起来可以超过capacity,但是不能超过maximum-capacity。

【反驳】

然而,没过多久,就收到了同事的答复邮件,并附带如下图示:队列配置10%的资源,用户提交了一个任务,使用的资源远超10%!

0.jpg

【回应】

收到邮件,瞬间觉得脸已被打肿,但是之前研究相关源码,确定应该是会限制的啊,难道是哪个细节没注意到,代码走了其他分支?

带着疑问再次走读相关代码,并进行一系列测试,发现该现象是可以解释的,之前给出的结论也仍旧还是成立的。

当前集群的总资源为12GB,队列容量设置为10%,因此该队列上,单个用户理论上资源使用的上限为:

12 * 1024 * 0.1 = 1228.8MB

注:该队列的父队列为root,如果父队列不是root,则需要继续乘父队列的容量百分比。

由于配置的集群资源分配最小单位为1024MB,因此需要向上取整,即2048MB。也就是单个用户使用的资源上限为2048MB。

当spark任务的driver启动时(申请的资源为2048MB),当前队列中,该用户已使用的资源为0,未超过上限,因此可以为其分配资源,即driver可以成功启动。

driver启动后继续申请启动两个executor,每个executor申请分配2048MB。yarn调度时,发现该用户当前已使用资源为2048MB(为driver分配的资源),仍旧未超过上限,因此继续为一个executor分配了资源。但轮到第二个executor时,该用户当前已使用的资源变为了4096MB,超过了上限,因此没有为该executor分配资源。

也就是说:虽然队列容量配置的是10%,但并不是严格按照10%来限制,即允许超额使用。只要用户当前已使用资源没有超过上限,就可以继续分配(即便分配后会超过上限);但一旦当前已使用的资源超过了上限时,则不能再继续分配资源。

为了验证上面的结论,再进行如下测试:队列的容量仍旧配置为10%,同时将AM资源使用限制调高(maximum-am-resource-percent),防止因AM资源受限出现干扰。

先提交一个spark任务,情况和上面的情况一样,再次提交一个任务时,第二个任务始终处于ACCEPT状态,spark任务的driver都没有进行资源分配。

1.jpg

同时,从界面上可以看到任务的诊断信息为:超过用户资源使用上限。

2.jpg

在这个基础之上,切换用户,再提交一个spark任务,发现任务可以正常运行,如下图所示:

3.jpg

将该队列的AM资源使用限制调回到原来的值,再来进行测试,第二个任务同样处于ACCEPT状态,但界面上看到的信息则不同,提示为:超过用户AM最大使用资源。

4.jpg

到这里,也就验证了之前的结论是正确的了。

【再次质疑】

将上面的测试过程,相关截图,以及结论总结进行了汇总,然后邮件进行了回复,以为可以告一段落了。但是,过了一会,再次收到了邮件,回复如下:

将队列的容量设置为5%,那么理论上该队列单个用户的最大使用资源为:

12 * 1024 * 0.05 = 614.4MB

向上取整为1024MB,按你的结论,提交的任务应该都无法分配资源,处于ACCEPT状态才对,然而还是进行了资源的分配,如下图所示: 5.jpg

【GG】

看到邮件后,心情很平淡,因为之前的研究过程中,已经发现了这个问题,直接贴一段代码说明:

if (!Resources.lessThanOrEqual(resourceCalculator, lastClusterResource,amIfStarted, amLimit)) {
    if (getNumActiveApplications() < 1
        || (Resources.lessThanOrEqual(resourceCalculator, lastClusterResource, queueUsage.getAMUsed(partitionName),Resources.none()))) {
      LOG.warn("maximum-am-resource-percent is insufficient to start a"
          + " single application in queue, it is likely set too low."
          + " skipping enforcement to allow at least one application"
          + " to start");
    } else {
      application.updateAMContainerDiagnostics(AMState.INACTIVATED, CSAMContainerLaunchDiagnosticsConstants.QUEUE_AM_RESOURCE_LIMIT_EXCEED);
      if (LOG.isDebugEnabled()) {
        LOG.debug("Not activating application " + applicationId
            + " as  amIfStarted: " + amIfStarted + " exceeds amLimit: "
            + amLimit);
      }
      continue;
    }
  }

也就是说,只要当前队列中没有任务在运行,提交任务时,即便是超过了用户可使用资源的上限,仍旧会进行资源的分配,保证有一个任务可以运行

另外,从上面的图中,还可以看出一点,该任务只分配了2048MB,也就是driver的资源,而driver申请启动的executor均未分配到任何资源,因为当前已使用资源已经超过了上限。

【总结】

队列的capacity参数是作用于单个用户的资源使用上限,真正调度分配时只要用户已使用资源未超过上限,就可以继续分配(分配后可以超过上限)。

当然决定用户资源使用上限的还有其他参数,例如user-limit-factor,minimum-user-limit-percent等,后续文章再单独说明。

另外,整个讨论过程下来,体会到源码是不会说谎的,看源码的同时还是要多动手测试验证,才能真正做到正确理解。