Yarn的调度器(三)

1. Yarn调度流程

1.1 Yarn分层调度

在 YARN 中资源分配共分成两个层级,第一层是全局应用的资源分配,第二个层
级在 ApplicationMaster 层面,ApplicationMaster 将从 RM 资源调度器那申请来的容器按照应用内调度算法分配给各个 task。下图为 YARN 分层调度架构:
在这里插入图片描述ApplicationMaster 首先向 ResourceManager 一次调度申请应用所需的资源,这
个是由 YARN 决定的,ApplicationMaster 获取资源后将资源二次调度分配给自己的 tasks,如 map 任务 reduce 任务等等。这个由 ApplicationMaster 决定,具体如何分配,不同的应用分配的方式也不同。

1.2 Yarn调度触发过程

YARN 调度器基于事件响应模型,事件响应模型由事件分发器事件处理器构成。YARN 中的调度器都继承了 AbstractService 抽象类,它是以服务的形式在ResourceManager 服务初始化时启动。ResouceManager 在构造初始化时生成事件派发器对象,同时在 ResouceManager 进行初始化过程中向事件派发器注册各种事件以及事件对应的处理器,如注册 RMAppEventType 对应的处理器。因为 YARN 调度器实现了 EventHandler接口,所以调度器就是一个事件处理器。这样当事件分发器中有调度事件发生时,分发器会取出这个事件交由注册的调度器进行处理。下表为调度器主要处理的事件类型:

事件类型 来源 描述
NODE_ADDED RMNode NM 节点向 RM 注册,调度器通过此事件增加此节点资源到整个集群资源
NODE_REMOVED RMNode NM节点向 RM 取消注册或 NM 无响应,调度器通过此事件减少集群资源
NODE_LABELS_UPDATE RMNode NM 节点通过心跳信息提供 labels 更新,调度器通过此事件完成资源标签更新
NODE_UPDATE RMNode NM 节点通过心跳信息更新节点资源,调度器根据此事件中资源参数更新集群资源
APP_ADDED RMApp RMApp创建后会通知调度器,调度器将该 app当前 app 集合中
APP_REMOVED RMApp RMApp 运行结束后会通知调度器,调度器将该app 从 app 集合中移除
APP_ATTEMP_ADDED RMAppAttempt RMAppAttempt创建后通知调度器,调度器将其添加到 appAttempt 集合中
APP_ATTEMPT_REMOVED RMAppAttempt AppAttempt 运行结束后,RMAppAttempt 通知调度器,调度器移除该 appAttempt

其中对于 NODE_UPDATE 事件,是在 NodeManager 定期向ResourceManager发送心跳信息时由 ResourceManager 端 RMNode 产生的,调度器从事件中获取RMNode 节点,首先处理节点更新信息,从 RMNode 上拉取 NM 端发送过来的心跳信息中最新的信息,例如先处理距上次心跳信息后新启动的容器列表,对于这些容器调度器在 RM 端将远程 Node 对应的RMNode 中该容器标记为已启动状态,并获取容器对应的 AppAttempt,在 AppAttempt 中获取对应的 RMContainer,并生成CONTAINER_LAUNCHED 消息分发给 RMContainer 做相应的处理。接着依次处理该心跳信息中已完成的容器列表,先将对应的 AppAttempt 中该容器移除,再从对应的 RMNode 中释放该容器,并将该容器占有的资源归还给节点,节点未使用资源将会加上该容器资源量。处理完容器更新信息后,调度器将发起调度,依据调度算法先选中资源队列,再从资源队列中选出调度的应用,从应用的调度信息中取出资源请求,在此 RMNode 上调度资源满足应用资源请求。通过源分析得出,当 NM 端每发送一次心跳信息,RM 端调度器将进行一轮资源调度。资源分配成功后,调度器将分配的容器资源添加到被调度应用的集合数据结构中供 ApplicationMaster 来获取。ApplicationMaster 是集群启动的第一个容器,它和其 他 的 容 器 不 同 , 它 是 由 ResourceManager 启 动 的 , 而 其 他 的 容 器 是 由ApplicationMaster 通过 ContainerManagerProtocol 通信协议在 NodeManager 上启动的。我们知道 ApplicationMaster 的运行对应着一个应用的运行。YARN 调度器分配的容器最终由 ApplicationMaster 端实现的 ApplicationMasterProtocol 协议的客户端远程调用 ResourceManager 端的 ApplicationMasterService 中的allocate 方法获取。allocate 方法会调用调度器的 allocate 方法,调度器的 allocate 方法参数中包括此次请求分配的容器列表和已完成的容器列表以及应用的appAttemptId。在调度器的allocate 方法中先处理已完成的容器列表,依次释放列表中的容器资源。接着将容器资源请求列表存放到应用的调度信息的数据结构中供调度器在下轮调度中选中该应用时取出该请求分配资源。最后通过 appAttemptId 获取应用,从该应用中获取 调 度 器 之 前 分 配 好 的 容 器 返 回 给 ApplicationMaster 。 可 以 看 出 , 当ApplicationMaster 调用 allocate 方法时返回的是之前请求分配的容器,不包括此次请求的容器。

2. Yarn调度器分析

2.1 FIFO调度器

在 Hadoop 刚出现时,Hadoop 运行的作业类型单一,仅仅为 MapReduce 任务,同时作业都是以批处理的方式进行提交处理,在实时调度方面也没有较高的要求,Hadoop 提供的 Fifo 调度器就能满足早期的需求。这种调度器调度算法比较简单,根据用户提交的作业的顺序,按顺序记录在一个队列中,当调度器进行调度时,依次遍历队列,获取对应的 AppAttempt,若当前分配资源的节点在当前遍历应用的黑名单中则继续遍历寻找下一个 AppAttempt,找到满足要求的 AppAttempt,从 AppAttempt 中获取调度请求,依据此调度请求中的需要的资源容器信息,在当前分配资源的节点上进行分配。需要注意的是对于同一个 AppAttempt 中的不同调度请求,是根据资源请求的优先级来进行调度的,优先级高的资源请求先被处理,若在同一个 AppAttempt 中高优先级的请求未满足时则跳过此 AppAttempt 调度,不能在同一个 AppAttempt中先满足低优先级的请求。一轮调度结束的条件是当前分配资源的节点可用资源小于最小分配阀值或者应用队列遍历结束。以下是该算法伪代码:

for app in app_queue:
	从 app 中获取 appAttempt;
	if appAttempt 不存在 or node placed in app blacklist:
		continue;
	end if
	for priority in appAttempt.getPriorites:
		n = assignContainersOnNode(node, appAttempt, priority)
		if n = 0:
			break;
		end if
	end for
	if node 可用资源小于 minimumAllocation:
		break;
	end if
end for

可以看出 Fifo 调度器的资源分配算法比较简单,app 层级严格按照先进先出的
顺序进行资源分配,app 内部按照优先级高低来分配满足资源请求。虽然这种调度器能满足早期的需求,但随着 Hadoop 架构的演变,YARN 开始支持多用户多应用类型共享集群资源,同时用户对应用的优先级也有一定的要求。这种 Fifo 调度方式很容易让先提交的低优先级大作业长时间占据集群资源,后提交的高优先级应用一直等待集群资源而无法得到运行,集群在有剩余资源的情况下,有应用请求资源确无法被满足,这大大降低了整个集群的资源使用率和集群的系统吞吐量。目前生产环境中很少应用 Fifo 调度器。

2.2 Capacity调度器

Capacity 调度器是由 Yahoo 公司开发贡献的,它基于资源容量的方式进行调度。支持多用户多种应用类型共享集群资源。它以队列的方式组织资源,用户将应用提交到相应的队列上。用户申请的资源受到此队列限制条件限制。如队列最大可用资源量,用户最大使用资源百分比等。队列之间的关系是以树型数据结构表示的,子队列继承父队列限制条件,另外子队列也拥有自己特定的限制条件。队列层级关系如下图所示:
在这里插入图片描述
整个集群中有一个根队列,根队列下面有多个子队列,最下层为叶子队列,用
户的应用最终会提交到叶子队列上。子队列以百分比来表示资源量。父队列拥有的资源量乘以这个子队列百分比即为子队列的容量。对于一个父队列 Qp,它的所有子队列 Qc1,Qc2,…Qcn ,用 C 表示队列容量,子队列容量百分比满足下面公式:

在这里插入图片描述

由此可知一个叶子队列的资源绝对容量百分比为从根节点出发到此叶子节点路径上所有队列容量连乘积,这个值乘以整个集群的资源容量就是这个队列的实际资源容量,所有叶子队列实际资源容量之和为集群的总资源容量。用户除了可以设置队列,设置队列的容量之外还可以对队列做如下限制:

  1. 设置所有队列处于运行或待运行状态应用最大数目即整个调度器可调度总应用数,也可以设置指定队列的应用数上限。
  2. 设置用户在该队列上最大使用资源百分比,用来限制用户滥用队列资源,从而更好的服务多租用户。
  3. 设置队列资源最大容量,允许队列使用资源超过设置的资源量,当其他队列需要资源时释放归还资源,弹性化占用资源更能充分利用空闲资源。
  4. 设置队列 ACL,指定哪些用户拥有向队列提交应用的权限。
  5. 设置队列 AM 占用资源百分比,一个 AM 的运行对应着一个应用的运行,从
    而间接限制运行中应用数目。

介绍完队列模型,接下来说一下调度策略。Capacity 调度器在分配容器过程中,第一步先选择队列,第二步选中队列中的应用,最后一步选择应用中资源请求。Capacity 调度器中选中应用是按照应用提交到队列先进先出的策略,选择应用中资源请求是按照请求的优先级来选择的。Capacity 调度器发挥其核心功能的地方在于第一步的选择队列。可想而知选择队列这一步在 ParentQueue 中实现,因为只有ParentQueue 才有子队列。对应的选择应用是在叶子队列上,因为所有的应用最终都是提交到叶子队列上。

通过阅读 YARN Capacity 调度器中 ParentQueue 的源码,ParentQueue 中以
Tree Set数据结构来存储子队列集合,Tree Set的排序规则按照传进去的比较器参数,继续跟踪比较器源码可以发现先通过队列的 getUsedCapacity()获取使用率,然后比较使用率,队列使用率小的排在前面优先选中,如果使用率相等则依据队列的名字字母顺序排序。需要注意的是这里面队列资源使用率是相对这个队列拥有资源的,不是相对整个集群的资源的。用 Rcluster 表示整个集群资源,Cp1,Cp2,…Cpn 表示从根队列到当前队列路径上队列节点的资源容量百分比,Rused 表示当前队列资源使用量,Uratio 可由下面公式计算得出:

在这里插入图片描述
由上面公式可以看出在选择队列时,并不是队列资源少就应该优先选择此队列,而是根据队列资源相对使用率的大小来确定的。根据资源使用率的大小来选择队列会使大容量队列和小容量的队列资源使用情况增长率趋于相等,若根据队列的容量大小来选择队列会导致小容量队列应用总是被先调度运行,大容量的队列上应用可能迟迟得不到调度运行。
以上为 Capacity 调度器的队列模型,下面介绍调度过程,Capacity 调度器调度过程中选择队列和应用的过程如下图所示:
在这里插入图片描述
Capacity 调度器在收到 NODE_UPDATE 事件后,与 FIFO 调度器处理方式一样,先处理该节点上的启动的容器列表,接着处理提升资源或执行类型的容器列表,最后处理完成的容器列表,释放容器,归还容器资源给集群。全部处理完后,发起调度流程。

首先调用调度器的 allocateContainersToNode(node)方法,在这个方法中先进行一些检验性工作,如检验调度器是否可以进行调度等。若验证通过,再接着检验该节点上剩余资源是否大于资源最小分配值。若大于,通过调度器中根队列引用root,调用其 assignContainers()方法。在 assignContainers 方法中,会对队列是否能访 问 该 node 以 及 队 列 是 否 有 资 源 请 求 进 行 验 证 ,若 验 证 不 通 过 则 返 回NULL_ASSIGNMENT。

接着在这个方法中调用 assignContainersToChildQueues 方法,在这个方法中,获取子队列集合迭代器,子队列按照上文中所说的资源利用率由低到高的顺序排序。继续调用各个子队列的 assignContainers 方法。可以看出这是个递归的过程,子队列调用 assignContainers 方法返回的分配清单会被添加到调用该子队列assignContainers 方法也即父队列的 assignContainers 方法中的分配清单里,最终会累加到根队列的分配清单中并返回给调度器。非叶子队列的 assignContainers 方法实际上是队列选择的过程,叶子队列的assignContainers 方法是应用的选择过程。

最后会调用叶子队列的 assignContainers 方法选择应用进行具体的容器分配。
在叶子队列的 assignContainers 方法中依次进行一些合法性检验,通过检验后,依次调度该叶子队列上的应用,Capacity 调度器中应用调度的顺序是先进先出的方式。对于当前的应用,先进行用户使用资源上限检验,若该应用所属的用户占用的队列资源达到上限,则跳过该应用。反之调用该应用的assignContainers 方法,每一个应用都有一个资源分配器,最终会调用资源分配器中 assignContainers 方法,在这个方法里按照应用资源请求的优先级进行分配具体的容器资源。应用级别的调度和 Fifo 调度器一样。以下是 Capacity 调度器算法伪代码。

按照资源利用率 sort child_queues
for q in root.child_queues:
	for qq in q.child_queues:
		按照资源利用率 sort child_queues
		...
	end for
end for
最终选择到 leaf_queue
按照 fifo 顺序 sort leaf_queue.applications
for app in applications:
	在 containerAllocator 中
	for priority in app.priorities:
		allocate(node, priority...)
	end for
end for	
最终返回 assignment

通过对 Capacity 调度器的描述,调度过程分析,可以发现 Capacity 调度器具有以下特性:

  1. 以层次化的队列组织资源,所有子队列可以使用父队列的全部资源,每个子队列又可以设置单独的资源限制,通过这种层次化队列结构,更能合理利用限制集群资源。
  2. 容量保证,每个队列都会设置一个资源容量百分比值,这个比值的资源是队列可以保证得到的资源。这样通过每个队列资源占比,能够限制资源不会被独占。
  3. 安全,队列可以设置用户访问 acl,每个用户只能访问自己有权限访问的队列,不能访问别的队列,只有管理员可以修改队列信息。
  4. 资源弹性分配,当集群有空闲资源,队列需要更多资源时,可以占用空闲资源,当别的队列受保证的资源无法得到满足时,归还资源。
  5. 多用户共享,多个用户通过队列共享整个集群资源,同时每个用户都能占有一定受保证的资源,尽最大可能满足所有用户需求,充分发挥集群能力,提高资源利用率。
  6. 便捷管理,YARN 提供一个可配置界面给管理员,方便管理员配置队列,同时可以动态增加队列,目前不支持动态删除队列。可以设置队列状态为
    STOPPED,限制应用提交到某队列,但不影响该队列上正在运行的应用。

2.3 Fair调度器

Fair 调度器是由 FaceBook 公司开发贡献的,整体架构上与 Capacity 调度器
类似,基于资源队列模型。Capacity 调度器在选择队列时是根据队列资源使用率,在选择应用时是按照先进先出的策略,而 Fair 调度器不同的是在选择队列和应用时都是按照某种公平算法来选择的。在 Fair 调度器中有两种公平算法,一种是最大最小公平算法,衡量的指标是内存资源。另一种是主资源公平算法,基于最大最小公平算法,衡量的指标是最主要的资源,可能是内存可能是 CPU 等。下一篇会详细介绍。

猜你喜欢

转载自blog.csdn.net/RivenDong/article/details/103364405