核心成员变量:
TaskSchedulingMgr mapScheduler = new MapSchedulingMgr(this);
Map任务的调度器
TaskSchedulingMgr reduceScheduler = new ReduceSchedulingMgr(this);
Reduce任务的调度器
MemoryMatcher memoryMatcher = new MemoryMatcher(this);
用与内存匹配
JobQueuesManager jobQueuesManager;
队列管理
CapacitySchedulerConf schedConf;
CapacityScheduler 的配置信息类
CapacityTaskScheduler 类实现了TaskScheduler类,实现核心方法为:
refresh()
start()
这是Mapreduce调度器的入口,在JobTracker的开始位置就是调用此方法,函数offerService()中有一句:
taskScheduler.start();
taskScheduler是通过反射完成实例化:
// Create the scheduler
Class extends TaskScheduler>schedulerClass
=conf.getClass("mapred.jobtracker.taskScheduler",
JobQueueTaskScheduler.class, TaskScheduler.class);
taskScheduler = (TaskScheduler)
ReflectionUtils.newInstance(schedulerClass, conf);
回到正题,start()的主要任务是:初始化配置信息,初始化队列,添加作业监听器(JobQueueManager)。
terminate()
终止调度器
assignTasks(TaskTracker)
声明如下:
public synchronized List
用一句话讲就是:通过一定的算法为给定的taskTracker分配计算任务,返回结果就是计算任务集合,一般情况下是一个Map任务和一个Reduce任务。
那么对于CapacityScheduler来说采用的算法策略是:当某个tasktracker上出现空闲slot时,调度器依次选择一个queue、(选中的queue中的)job、(选中的job中的)task,并将该slot分配给该task。这些过程已经在上面的TaskScheduling及其实现类的相关方法中讨论过了。
getJobs(String)
声明:
public synchronized Collection
根据队列名获取队列中的作业集合。
其他不是从TaskScheduler继承来的,主要是辅助作用,比较重要的方法如下:
checkForQueueDeletion(Map
这个方法只有在刷新(refresh())的时候会被调用。调度器重新读取配置文件后,比较现有的队列信息和新的队列信息是否一致,如果不一致则抛出异常。
initialize(QueueManager,
Map
Configuration,CapacitySchedulerConf)
主要是初始化内存相关信息,初始化队列信息,通知相关对象(作业管理器,mapScheduler,reduceScheduler)更新队列信息。
initializeMemoryRelatedConf(Configuration)
初始化内存相关的配置信息。
Map
CapacitySchedulerConf)
解析队列信息,最后形成一个队列名和队列对象的映射关系。
jobAdded(JobInProgress)
监听,作业加入时被执行。
preInitializeJob(JobInProgress)
在jobAdded()中被调用,更新作业的单个Map/Reduce任务所需的slot数。
jobCompleted(JobInProgress)
在作业完成时被调用,通知该作业的队列。
只有一个变量:
private CapacityTaskScheduler scheduler
在构造器中传入调度器对象。
函数:
isSchedulingBasedOnMemEnabled()
调度算法是否支持内存的调度
getMemReservedForTasks(TaskTrackerStatus,TaskType, int)
获取给定的TaskTracker的内存使用情况。计算方法是依据TaskTracker的上报信息(TaskStatus)来计算。
currentlyScheduled(TaskTrackerStatus,TaskType, int)
被getMemReservedForTasks调用,计算当前的TaskTracker被占用的slot数
matchesMemoryRequirements(JobInProgress, TaskType, TaskTrackerStatus, int)
这是MemoryMatcher的核心函数,主要判断TaskTracker是否能满足特点job的内存需求。
关于作业的初始化,初始化的逻辑是由一个主线程和几个工作线程构成,其中,主线程周期性的从scheduler中“拖”一部分作业,把这些作业赋给工作线程,让工作线程去完成。“拖”选作业的原则是作业被调度的可能性较大,作业被调度的可能性主要:用户的作业限制,队列的容量限制。同时,高优先级的作业总是被优先初始化。
作业的初始化工作需要占用JobTracker的内存,因此有必要限定每个队列的初始化作业的数量。
类包含的主要成员变量如下:
private JobQueuesManager jobQueueManager; 作业管理器
private long sleepInterval; 线程间隔时间
private int poolSize; 初始化规模数
HashMap<JobID,JobInProgress> initializedJobs;已经初始化的作业集合
private volatile boolean running; 正在运行与否
private TaskTrackerManager ttm;
private Map
在讨论函数前,先把它的内部类搞定。
类JobInitializationThread
private JobInProgress initializingJob; 正在初始化的作业
private volatile boolean startIniting; 是否开始初始化
private AtomicInteger currentJobCount = new AtomicInteger(0); 初始化作业数,它是一个原子类型。
我们从执行流程来分析JobInitializationThread。
首先肯定是run()函数,它只调用了initializeJobs(),这个函数的主要工作是:
循环执行:
获取每个队列的首个任务进行初始化,初始化工作由TastTrackerManager的initJob()函数完成。
这就是JobInitializationThread的核心。其它函数比较简单,在此不讨论了。
回到主类,它的核心函数是:
init(int, CapacitySchedulerConf)
初始化函数相当重要,它直接被CapacityTaskScheduler的start()函数调用。我们看看它是怎么折腾的。首先,确定两个变量sleepInterval(线程执行的睡眠时间),poolSize(取初始化线程数(有配置文件的“mapred.capacity-scheduler.init-worker-threads”决定,默认值是5)和队列大小中的较小值)的大小。其次,为队列分配初始化线程(工作线程),见函数assignThreadsToQueues()。最后,启动所有工作线程。
assignThreadsToQueues()
服务于每个队列的初始化线程的分配工作,由这个函数搞定。首先创建poolSize个线程,每个线程服务${队列数/poolSize}个队列。当然,这样分配会发现有些队列没有分配线程,因此,再按照roundrobin算法(从poolSize个线程中挑选)为队列分配线程。
run()
循环:
1.清除已经初始化的作业列表
2.选择即将初始化的作业(调用selectJobsToInitialize())
selectJobsToInitialize()
主要干活的是接下来的函数
getJobsToInitialize(String)
选择作业的核心是这个函数,参数是队列名称。
循环迭代队列的每一个作业:检查作业是否已经被初始化;检查队列最大初始化作业数的限制是否满足;用户限制是否满足;确认该作业还没有被kill掉。最后,将作业加入初始化队列。
队列管理类,比较简单。主要是管理队列,维护队列,用户,资源使用率等信息,这些信息作为调度算法的调度依据。每个队列有最大计算能力百分比(maximum-capacity percent)限制,同时还实时维护了一个计算能力百分比(capacityPercent)变量。
显示调度信息,servlet。