当某个tasktracker上出现空闲slot时,调度器依次选择一个queue、(选中的queue中的)job、(选中的job中的)task,并将该slot分配给该task。下面介绍选择queue、job和task所采用的策略:
(1)选择queue:将所有queue按照资源使用率(numSlotsOccupied/capacity)由小到大排序,依次进行处理,直到找到一个合适的job。
(2)选择job:在当前queue中,所有作业按照作业提交时间和作业优先级进行排序(假设开启支持优先级调度功能,默认不支持,需要在配置文件中开启),调度依次考虑每个作业,选择符合两个条件的job:[1] 作业所在的用户未达到资源使用上限 [2] 该TaskTracker所在的节点剩余的内存足够该job的task使用。
(3)选择task,同大部分调度器一样,考虑task的locality和资源使用情况。(即:调用JobInProgress中的obtainNewMapTask()/obtainNewReduceTask()方法)
参考自:http://dongxicheng.org/mapreduce/hadoop-capacity-scheduler/
类的静态结构
CapacityTaskScheduler是核心类,我们先看它的辅助类
CapacityTaskScheduler的辅助类都是内部类,下面逐个描述:
它是任务调度管理,抽象类,包括三个成员:
protected CapacityTaskScheduler scheduler;
protected TaskType type = null;
privateList queuesForAssigningTasks =
newArrayList();
其中,scheduler是调度器对象,在构造函数中传入;type代表任务类型,在TaskSchedulingMgr的实现类MapSchedulingMgr和ReduceSchedulingMgr中分别取值为TaskType.Map和TaskType.Reduce。第三个重要的变量是queuesForAssigningTasks,这是即将要分配任务的队列,变量中的队列对象按照:正在运行的任务数/容量,进行排序。
TaskSchedulingMgr的抽象方法有:
obtainNewTask(TaskTrackerStatus, JobInProgress, boolean)
getSlotsOccupied(JobInProgress)
getClusterCapacity()
getSlotsPerTask(JobInProgress)
getRunningTasks(JobInProgress)
getPendingTasks(JobInProgress)
getNumReservedTaskTrackers(JobInProgress)
hasSpeculativeTask(JobInProgress, TaskTrackerStatus)
抽象方法到它的实现类去讨论。
其它普通方法:
hasSufficientReservedTaskTrackers(JobInProgress)
简单方法,检查 tasktracker数是否大于等于改作业未执行的任务数。
getOrderedQueues():得到所有队列的名称的串,主要用于测试。
initialize(Map
初始化队列信息,并排序(sortQueues)
sortQueues()
对queuesForAssigningTasks成员排序,比较器为MapQueueComparator和ReduceQueueComparator。
divideAndCeil(inta, int b): a除b正向取整。
TaskLookupResult getTaskFromQueue(TaskTracker,
int,CapacitySchedulerQueue,boolean)
这是一个非常重要的方法,这个方法从一个队列中取出一个task,返回的TaskLookupResult对象包含:
private LookUpStatus lookUpStatus;
private Task task;
private JobInProgress job;
其中task才是关键信息,当然,task可能为空。具体的实现逻辑是:
(1)在队列中的选一个job,检查是否是RUNNING状态的;
如果不是,重复(1),否则继续;
(2)检查队列是否超出最大slot限制,或者用户的slot占有量是否超过限定,如果是则回到(1),否则继续;
(3)检查tasktracker是否能满足作业的内存需求,如果满足则从作业中挑选一个任务,如代码:
TaskLookupResult tlr = obtainNewTasktask(TrackerStatus,j,assignOffSwitch);
obtainNewTask()函数负责从job中未运行的任务挑选一个,该函数的具体细节容稍后讨论。 如果没能从job中找到适合的任务,则回到(1),否则成功,返回结果,结束。
如果tasktracker的内存不能满足作业的内存需求,那么调度器会判断该作业是否有待运行的任务或者保留的tasktracker是否足够。如果是则回到(1),否则,将该tasktracker保留,退出函数。这里有一个机制:CapacitySecheduler在调度过程中考虑作业的内存需求,但是,当tasktracker内存无法满足job的内存需求时,系统不会把它直接放弃该tasktracker,而是将它保留给该作业,也就是将该作业的tasktracker的slot登记到job的名下,这样做是为了使该作业不至于饿死。
assignTasks(TaskTrackertaskTracker,
int availableSlots,
boolean
assignOffSwitch)
输入参数:tasktracker;
availableSlots 是tasktracker剩余的Map或Reduce的slot数;
assignOffSwitch 为false表示局部性,true表示非局部性。
分两种情况,第一是,看taskTracker是否已经被预定了:
JobInProgress job =taskTracker.getJobForFallowSlot(type);
如果job不为空,这表示taskTracker的slot被job预定了,这时做以下工作:
如果availableSlots大于等于每个task所需的slot数,那么taskTracker释放预留的slot,并从job中挑选一个任务在taskTracker上运行,退出。否则,重新预留taskTracker的slot给job,并返回内存匹配失败的TaskLoogupResult对象。
第二种情况,如果taskTracker没有被预留,那么,从备选的队列集合中寻找适合的队列,并从队列中挑选适合的task,选出来的task必须是三种类型中的一种:LOCAL_TASK_FOUND(具有本地局部性),OFF_SWITCH_TASK_FOUND(不具有局部性)或者TASK_FAILING_MEMORY_REQUIREMENT(挑选的任务内存需求得不到满足)。挑选过程的执行代码是:
askLookupResult tlr =getTaskFromQueue(taskTracker, availableSlots,
queue, assignOffSwitch);
TaskLookupResult.LookUpStatus lookUpStatus =tlr.getLookUpStatus();
如果以上三种都不是,则换一个队列,继续找。如果最终还没有找到,返回任务查找失败。
printQueues()
打印作业队列信息,用于调试。
hasSpeculativeTask(TaskInProgress[], float,TaskTrackerStatus)
检查tasktracker是否已经被某个作业预留了。
两个类都继承自TaskSchedulingMgr,实现了抽象方法,我们先讨论抽象方法在类MapSchedulingMgr中的实现:
obtainNewTask(TaskTrackerStatus,JobInProgress, boolean)
这个函数是针对TaskTracker,从job中挑选一个task,boolean是表示是否要满足局部性的要求。首先,试图查找本地任务;其次,如果失败则找非本地任务,最后返回结果。
下面几个函数都是从简单的读取相应的变量,比较简单所以不讨论。
getClusterCapacity() 读取集群的容量,即slot数
getRunningTasks(JobInProgress) 读取作业正在运行的任务数
getPendingTasks(JobInProgress) 读取作业等待运行的任务数
getSlotsPerTask(JobInProgress) 每个任务所需要的slot数
getNumReservedTaskTrackers(JobInProgress) 为作业预留的taskTracker数
hasSpeculativeTask(JobInProgress,TaskTrackerStatus) 作业在taskTracker上是否有预留任务。
下面讨论ReduceSchedulingMgr类。
obtainNewTask(TaskTrackerStatus, JobInProgress,boolean)
它的实现看上去比较简单,只有十行以内的代码。
ClusterStatus clusterStatus =
scheduler.taskTrackerManager.getClusterStatus();
int numTaskTrackers = clusterStatus.getTaskTrackers();
Task t =job.obtainNewReduceTask(taskTracker, numTaskTrackers,
scheduler.taskTrackerManager.getNumberOfUniqueHosts());
return (t != null) ? TaskLookupResult.getTaskFoundResult(t, job) :
TaskLookupResult.getNoTaskFoundResult();