前言:为了研究需要,将Capacity Scheduler和Fair Scheduler的原理和代码进行学习,用两篇文章作为记录。如有理解错误之处,欢迎批评指正。
容量调度器(Capacity Scheduler)是Yahoo公司开发的多用户调度器。多用户调度器的使用场景很多,根据资料1的说法,Hadoop集群的用户量越来越大,不同用户提交的应用程序具有不同的服务质量要求(QoS):
1. 批处理作业:耗时较长,对完成时间没有严格要求。如数据挖掘、机器学习等应用。
2. 交互式作业:期望及时返回结果。如Hive等应用。
3. 生产性作业:要求一定量的的资源保证。如统计值计算、垃圾数据分析等。
传统的FIFO调度器不能满足应用对响应时间和资源的多样化要求,多用户多队列调度器应运而生。容量调度器即是其中被广泛应用的一种。
一、基本思想
容量调度器以队列为单位划分资源,每个队列都有资源使用的下限和上限。每个用户也可以设定资源使用上限。一个队列的剩余资源可以共享给另一个队列,其他队列使用后还可以归还。管理员可以约束单个队列、用户或作业的资源使用。支持资源密集型作业,可以给某些作业分配多个slot(这是比较特殊的一点)。支持作业优先级,但不支持资源抢占。
这里明确一下用户、队列和作业之间的关系。Hadoop以队列为单位管理资源,每个队列分配到一定的资源,用户只能向一个或几个队列提交作业。队列管理体现为两方面:1. 用户权限管理:Hadoop用户管理模块建立在操作系统用户和用户组之间的映射之上,允许一个操作系统用户或者用户组对应一个或者多个队列。同时可以配置每个队列的管理员用户。队列信息配置在mapred-site.xml文件中,包括队列的名称,是否启用权限管理功能等信息,且不支持动态加载。队列权限选项配置在mapred-queue-acls.xml文件中,可以配置某个用户或用户组在某个队列中的某种权限。权限包括作业提交权限和作业管理权限。2. 系统资源管理:管理员可以配置每个队列和每个用户的可用资源量信息,为调度器提供调度依据。这些信息配置在调度器自己的配置文件(如Capacity-Scheduler.xml)中。关于每个配置文件的常见内容见附录。
二、整体架构
总体来说,容量调度器的工作流程分5个步骤:
1. 用户提交作业到JobTracker。
2. JobTracker将提交的作业交给Capacity Scheduler的监听器JobQueuesManager,并将作业加入等待队列,由JobInitializationPoller线程初始化。
3. TaskTracker通过心跳信息要求JobTracker为其分配任务。
4. JobTracker调用Capacity Scheduler的assignTasks方法为其分配任务。
5. JobTracker将分配到的任务返回给TaskTracker。
接下,我们结合源代码依次研究上述过程。
三、实现细节
1. 调度器的启动
回忆一下,前面谈到调度器启动是由JobTracker调用调度器的start方法实现的,首先来看start方法:
// initialize our queues from the config settings
if (null == schedConf) {
schedConf = new CapacitySchedulerConf();
}
首先生成配置对象,容量调度器定义了自己的配置对象,构造时会加载调度器自己的配置文件作为资源,并初始化一些默认的配置选项:
public CapacitySchedulerConf() {
rmConf = new Configuration(false);
rmConf.addResource(SCHEDULER_CONF_FILE);
initializeDefaults();
}
private void initializeDefaults() {
defaultUlimitMinimum =
rmConf.getInt(
"mapred.capacity-scheduler.default-minimum-user-limit-percent", 100);
defaultUserLimitFactor =
rmConf.getFloat("mapred.capacity-scheduler.default-user-limit-factor",
1.0f);
defaultSupportPriority = rmConf.getBoolean(
"mapred.capacity-scheduler.default-supports-priority", false);
defaultMaxActiveTasksPerQueueToInitialize =
rmConf.getInt(
"mapred.capacity-scheduler.default-maximum-active-tasks-per-queue",
200000);
defaultMaxActiveTasksPerUserToInitialize =
rmConf.getInt(
"mapred.capacity-scheduler.default-maximum-active-tasks-per-user",
100000);
defaultInitToAcceptJobsFactor =
rmConf.getInt("mapred.capacity-scheduler.default-init-accept-jobs-factor",
10);
}
例如,第一个默认值表示每个用户的最低资源保障,默认为100%。第三个默认值表示是否考虑作业优先级,默认是不考虑。其他配置可以参考资料1中的讲解。接下来,初始化队列信息,队列信息由QueueManager对象获得,该对象的构造过程如下:
public QueueManager(Configuration conf) {
checkDeprecation(conf);
conf.addResource(QUEUE_ACLS_FILE_NAME);
// Get configured ACLs and state for each queue
aclsEnabled = conf.getBoolean("mapred.acls.enabled", false);
queues.putAll(parseQueues(conf));
}
synchronized private Map<String, Queue> parseQueues(Configuration conf) {
Map<String, Queue> queues = new HashMap<String, Queue>();
// First get the queue names
String[] queueNameValues = conf.getStrings("mapred.queue.names",
new String[]{JobConf.DEFAULT_QUEUE_NAME});
for (String name : queueNameValues) {
Map queueACLs = getQueueAcls(name, conf);
if (queueACLs == null) {
LOG.error("The queue, " + name + " does not have a configured ACL list");
}
queues.put(name, new Queue(name, getQueueAcls(name, conf),
getQueueState(name, conf), QueueMetrics.create(name, conf)));
}
return queues;
}
首先,获取用户权限配置文件mapred-queue-acls.xml。然后通过mapred-site.xml中的配置解析并生成队列的列表queues。解析的过程是,先获取每个队列的名字,再通过名字获取队列的权限配置,最后依据这些信息以及队列状态和队列度量对象构造一个队列并加入结果列表。如上面代码。在初始化队列之前还有构造出每个队列对应的CapacitySchedulerQueue对象:
Map<String, CapacitySchedulerQueue>
parseQueues(Collection<String> queueNames, CapacitySchedulerConf schedConf)
throws IOException {
Map<String, CapacitySchedulerQueue> queueInfoMap =
new HashMap<String, CapacitySchedulerQueue>();
// Sanity check: there should be at least one queue.
if (0 == queueNames.size()) {
throw new IllegalStateException("System ha