原文链接:http://click.aliyun.com/m/13951/

各位好,这是介绍阿里云伏羲(fuxi)调度器系列文章的第二篇,今天主要介绍调度模型和FIFO\FAIR调度策略

一、FuxiMaster简介

FuxiMaster和Yarn非常相似,定位于分布式系统中资源管理与分配的角色:一个典型的资源分配流程图如下所示: 




作为调度器,目前FuxiMaster支持的功能主要有:

1、多租户管理

2、支持FIFO/FAIR调度策略(本文)

3、针对在线服务保持资源强稳定

4、支持NodeLabel动态划分集群

5、支持多机房调度

6、支持基于优先级的交互式抢占

7、支持AllOrNothing调度

8、支持基于硬件ID化的调度

9、单Master目前支持2w台机器的规模

10、......

二、基本调度单元与基于拓扑的调度语义.

1、基本调度单元:

在fuxi系统内,基本的调度单元称作ScheduleUnit,它的概念和Yarn的Container是不同的: 举个例子,假设一个MR的作业,规模是1000*1000, 那么在Yarn的调度器中,对应就有100,000个调度单元;而在fuxi系统里,只有2个调度单元(ScheduleUnit), 每一个SchedsuleUnit的SlotNumber是1000。在调度层面上,ScheduleUnit是同一类Slot的集合

2、基本调度语义:

在fuxi系统内,目前线上存在3种基于拓扑的调度语义:LT_MACHINE\LT_ENGINEROOM\LT_CLUSTER, 分别对应着指定机器、指定机房、全集群任选集群;且调度的优先级是LT_MACHINE > LT_ENGINEROOM > LT_CLUSTER; 一个典型的资源申请请求为: SchduleUnit{SlotNum:5, M1 *1, M2 * 1, M3 *1, M4 *1, M5 * 1, C * 5}, 这个ScheduleUnit理解为:总共需要5个slot,优先在M1\M2\M3\M4\M5上分配资源,如果这些机器资源不满足的话,也可以退而其次在其他机器上(LT_CLUSTER)上分配资源

三、主动调度策略:

作业第一次将SchduleUnit发送到调度器时,调度器会遍历ScheduleUnit的拓扑语义在对应机器上进行调度,对应LT_MACHINE的语义,会直接到指定机器上尝试分配资源;如果是LT_ENGINEOOM\LT_CLUSTER的语义,则在一组满足条件的机器列表内进行RoundRobin的分配(roundrobin);

除此之外,还有一些额外的分配限定:

1、ScheduleUnit如果是属于某个QuotaGroup的,那么会首先根据这个QuotaGroup的剩余可用Quota / ScheduleUnit体积 得出一个从Quota层面可以分配的slot数目,与ScheduleUnit的desireNum取一个min;

2、ScheduleUnit可以定义在同一台机器上分配的最大worker数目,主要防止相同类型的worker扎堆在同一台机器上;

3、如果机器处于ScheduleUnit的黑名单中,那么这台机器也不会被分配;黑名单的来源有2种,一种是集群中PE加入的全局黑名单,这个对所有SscheduleUnit都是不可用的;一种的ScheduleUnit自己的黑名单,通常是一台机器多次出现slot运行失败,则作业会通知调度器暂时不调度新的slot到这台机器上;

主动调度策略从全局来说是一种贪心的调度策略,尽量对ScheduleUnit进行调度,如果ScheduleUnit没有被完全满足,则ScheduleUnit携带剩余的DesireNum进入到排队队列,等待被动调度策略触发调度

四、被动调度策略:

被动调度策略顾名思义,是处于waitingQueue中的SchduleUnit被动的被调度器挑选中分配资源;触发被动调度策略的条件有2个:一个是跑完的作业归还资源;一个是机器的资源增加;即当有额外的可用资源时,就会触发被动调度策略,在内部有一个更形象的名字,称为”OnResourceFree“

1、如何挑选waitingQueue

WaitingQueue是基于QuotaGroup的,每一个QuotaGroup都有自己的waitingQueue,同组的ScheduleUnit只会插入到自己组的waitingQueue中;当有一台机器有剩余资源时,我们挑选哪一个QuotaGroup的waitingQueue进行分配呢? 在FuxiMaster中,QuotaGroup有“Hungry”的概念,Hungry的定义是:usdQuota/maxQuota(概念参见上一篇),此值越低,表明这个QuotaGroup越饥饿,越应该优先得到满足(这里我们也在讨论是否参考runtimeQuota更合理); 根据Hungry对所有QuotaGroup进行排序后,我们就可以得出一个waitingQueue的分配顺序

2、WaitingQueue的构成及遍历

WaitingQueue存放着没有被满足的ScheduleUnit,SchdeduleUnit排列的顺序是根据ScheduleUnit的优先级决定的: 每一个ScheduleUnit都被作业赋予了一个优先级,优先级越高,表明越应该优先分配资源,故在waitingQueue中的位置就越靠前。

在具体分配过程中,对于每一个ScheduleUnit的分配是贪心的,也收到在主动调度策略中的各种限制,一种典型的分配场景如下图所示:




在上图中,如果我们根据优先级依次对ScheduleUnit尝试分配时,发现处于前4个的ScheduleUnit的体积都比机器的可用资源大,那么总共产生80%的无用遍历,当队列中ScheduleUnit比较多时,这个遍历的代价是比较大的,时间复杂度是O(N), 为此,我们采用了如下的算法,期望能够直接找到从资源维度能够分配的ScheduleUnit,同时满足优先级的约定:


首先,我们根据ScheduleUnit的CPU体积构建子队列,每个子队列的ScheduleUnit CPU体积相同,且根据优先级进行排列;同时根据可用资源取出每个候选队列的对头的ScheduleUnit,构建成堆;



当对第一个元素进行分配并POP后,如果堆头ScheduleUnit的体积大于剩余资源,则直接POP;同时尝试将上一个POP出去的ScheduleUnit所属队列的下一个ScheduleUnitPush进堆;


继续:


直到绿线比最低的虚线还要低,表示无法在分配,算法结束



当然,同一条虚线上的ScheduleUnit虽然在CPU维度满足,但是在MEM维度还是可能不满足,所以还是可能会有很多无用遍历,还有优化的空间:



0、红黑树节点保存ScheduleUnit的指针,排序的key是priority

1、每个节点保存自己左、右子树的ScheduleUnit SlotDesc MEM的最小值;

2、先看左子树,如果左子树的ScheduleUnit SlotDesc MEM的最小值比可用资源MEM的值小,表示左子树中有可分的、高优先级的SU,向左子树递归;

3、如果左子树不满足,则看自己满足不满足

4、如果自己不满足,则看右子树的SU SlotDesc MEM的最小值是否比可用资源, MEM的值小,表示左右子树中有可分的、低优先级的SU,向右子树递归;

5、如果都不满足,则此树上所有节点都不再可能被分配资源,以后就不用在遍历此树了


这样做之后,我们就可以以O(logN)的代价找到优先级最高的、CPU、MEM也满足条件的ScheduleUnit,下面一组实验表明了算法的优越性:




在上述实验中,可分的ScheduleUnit只有一个,剩余的ScheduleUnit的体积全部比剩余资源大。可以看到,优化方案比普通遍历方案在性能上提升非常明显

3、FIFO\FAIR调度策略

FIFO\FAIR调度策略的却别体现在WaitingQueue的排序的Key: 如果ScheduleUnit的优先级不同,那么两者都会优先对高优先级的ScheduleUnit进行分配;当优先级相同时,FIFO是根据ScheduleUnit的提交时间进行排序的,提交时间越早,优先级越高; 而FAIR是根据已经分配到的SlotNum进行排序的,已经分配的slotNum越小,优先级越高。这样对FAIR组而言,基本保证了同优先级ScheduleUnits拿到的资源份数基本是相同的