Job的初始化—EagerTaskInitializationListener
来源:互联网 发布:网络编程教程 编辑:程序博客网 时间:2024/06/08 09:09
客户端用户在向JobTracker节点成功地提交了一个Job之后,JobTracker节点并不会马上对这个作业进行调度,因为任何作业在被任务调度器TaskSchedule调度之前,都必须先被初始化,这一点笔者曾经在讲解Hadoop的默认调度器——JobQueueTaskSchedule时已经详细地谈到过了。而本文将要集中讨论的是Job是如何被初始化的,同时,Job的初始化又干了哪些事情。
对于Job的初始化,JobTracker采用了Observer的设计模式,也就是说JobTracker并没有在用户的响应线程中对该用户提交的Job进行初始化处理,而是交给了一个Listener来做,JobTracker节点之所以会这样设计,是因为Job的初始化处理会影响用户的响应时间(Job在初始化是会从DFS上本地化与该Job相关的文件)。这个过程其实很简单,就是当一个用户提交了一个Job之后,JobTracker会把这个Job的初始化工作结果一个后台线程来做,而主线程可以直接返回了,即可结束对用户的提交Job请求处理。这个后台线程就是EagerTaskInitializationListener。
先来看看与EagerTaskInitializationListener相关的类吧!
JobTracker节点把所有成功提交的Job都交给了EagerTaskInitializationListener来初始化,所以为了提高自身初始化Job的吞吐量,EagerTaskInitializationListener在其内部设计了多线程的方式,每一个Job都会用一个线程来为它初始化,但是考虑到JobTracker节点系统资源的限制,它又折中的选择了线程池。EagerTaskInitializationListener内部默认的线程池大下小为4,不过也可以通过JobTracker的配置文件来设置,对应的配置项为:mapred.jobinit.threads。对于频繁提交小型作业的应用系统来说,适当地增大线程池的容量可以有效的提供高系统的工作效率。这个简单的过程如下图:
那么,Job的初始化工作主要是对该Job做哪些初始化工作呢?这个初始化工作主要是加载Job对应的split文件,因为这个split文件包含了作业的输入数据的切片,而每一个数据切片又对应的一个Map任务,请注意,这个split文件是存储在Map-Reduce所以来的分布式文件系统上的,所以前面说Job的初始化会耗费一些时间。根据split文件中的数据切片数量以及每一个切片所在的存储节点的位置,就可以创建该Job的所有Map任务了,创建了Map任务之后顺带把Reduce任务也给创建了,不过除了Job真正的Map/Reduce任务之外,一个Job还包括额外的四个任务:Setup/Cleanup Map/Reduce Task。Setup Map/Reduce Task任务分别用来启动Map/Reduce任务,即为真正的Map/Reduce任务的执行做准备,如为这些任务创建数据存放目录;Cleanup Map/Reduce Task任务被分别用来清除与Map/Reduce任务相关的数据和目录。这个Job初始化的源代码如下:
public synchronized void initTasks() throws IOException, KillInterruptedException { //作业已经被初始化了或者已经被完成了 if (tasksInited.get() || isComplete()) { LOG.debug("Job["+jobId+"] has been inited or compelted, so return"); return; } synchronized(jobInitKillStatus){ if(jobInitKillStatus.killed || jobInitKillStatus.initStarted) { return; } jobInitKillStatus.initStarted = true; } LOG.info("Initializing " + jobId); // log job info JobHistory.JobInfo.logSubmitted(getJobID(), conf, jobFile.toString(), this.startTime, hasRestarted()); // log the job priority setPriority(this.priority); // 加载作业的split文件 String jobFile = profile.getJobFile(); Path sysDir = new Path(this.jobtracker.getSystemDir()); FileSystem fs = sysDir.getFileSystem(conf); Path _splitFile= new Path(conf.get("mapred.job.split.file")); DataInputStream splitFile = fs.open(_splitFile); JobClient.RawSplit[] splits; try { splits = JobClient.readSplitFile(splitFile); } finally { splitFile.close(); } numMapTasks = splits.length;//map任务的数量 // if the number of splits is larger than a configured value then fail the job. int maxTasks = jobtracker.getMaxTasksPerJob(); if (maxTasks > 0 && numMapTasks + numReduceTasks > maxTasks) { throw new IOException("The number of tasks for this job " + (numMapTasks + numReduceTasks) + " exceeds the configured limit " + maxTasks); } jobtracker.getInstrumentation().addWaiting(getJobID(), numMapTasks + numReduceTasks); //创建作业的Map任务 maps = new TaskInProgress[numMapTasks]; for(int i=0; i < numMapTasks; ++i) { inputLength += splits[i].getDataLength(); maps[i] = new TaskInProgress(jobId, jobFile, splits[i],jobtracker, conf, this, i); } LOG.info("Input size for job " + jobId + " = " + inputLength + ". Number of splits = " + splits.length); if (numMapTasks > 0) { nonRunningMapCache = createCache(splits, maxLevel); } //设置作业运行的开始时间 this.launchTime = System.currentTimeMillis(); // 创建作业的Reduce任务 this.reduces = new TaskInProgress[numReduceTasks]; for (int i = 0; i < numReduceTasks; i++) { reduces[i] = new TaskInProgress(jobId, jobFile, numMapTasks, i, jobtracker, conf, this); LOG.debug("creae a Reduce Task["+reduces[i].getTIPId()+"] for Job["+jobId+"]."); nonRunningReduces.add(reduces[i]); } // Calculate the minimum number of maps to be complete before // we should start scheduling reduces completedMapsForReduceSlowstart = (int)Math.ceil((conf.getFloat("mapred.reduce.slowstart.completed.maps", DEFAULT_COMPLETED_MAPS_PERCENT_FOR_REDUCE_SLOWSTART) * numMapTasks)); // create cleanup two cleanup tips, one map and one reduce. cleanup = new TaskInProgress[2]; // cleanup map tip. This map doesn't use any splits. Just assign an empty // split. JobClient.RawSplit emptySplit = new JobClient.RawSplit(); cleanup[0] = new TaskInProgress(jobId, jobFile, emptySplit, jobtracker, conf, this, numMapTasks); cleanup[0].setJobCleanupTask(); LOG.debug("create a Cleanup Map Task["+cleanup[0].getTIPId()+"] for Job["+jobId+"]"); // cleanup reduce tip. cleanup[1] = new TaskInProgress(jobId, jobFile, numMapTasks,numReduceTasks, jobtracker, conf, this); cleanup[1].setJobCleanupTask(); LOG.debug("create a Cleanup Reduce Task["+cleanup[1].getTIPId()+"] for Job["+jobId+"]"); // create two setup tips, one map and one reduce. setup = new TaskInProgress[2]; // setup map tip. This map doesn't use any split. Just assign an empty // split. setup[0] = new TaskInProgress(jobId, jobFile, emptySplit, jobtracker, conf, this, numMapTasks + 1 ); setup[0].setJobSetupTask(); LOG.debug("create a Setup Map Task["+setup[0].getTIPId()+"] for Job["+jobId+"]"); // setup reduce tip. setup[1] = new TaskInProgress(jobId, jobFile, numMapTasks, numReduceTasks + 1, jobtracker, conf, this); setup[1].setJobSetupTask(); LOG.debug("create a Setup Reduce Task["+setup[1].getTIPId()+"] for Job["+jobId+"]"); synchronized(jobInitKillStatus){ jobInitKillStatus.initDone = true; if(jobInitKillStatus.killed) { throw new KillInterruptedException("Job " + jobId + " killed in init"); } } tasksInited.set(true);//标记该作业已经初始化了 JobHistory.JobInfo.logInited(profile.getJobID(), this.launchTime, numMapTasks, numReduceTasks); }从上面的源码可以看出,作业的Map任务的数量是由输入数据的切片数量决定的,而Reduce任务的数量是由用户在提交Job之前决定的。但是,一个Job的作业总数量(这里指真正Map/Reduce的任务和)不能超过JobTracker节点规定的上限,这个上限值可以由配置文件来设置,对应的配置项为:mapred.jobtracker.maxtasks.per.job,负数值表示无穷大。不过,在Job的初始化过程中还要注意的一个问题是,如果作业有Map的任务的话,在创建创建Reduce任务之前还会调用createCache()方法,那么这个方法是用来干嘛的呢?这个方法用来预分配作业Map任务的本地调度范围,这个本地范围与JobTracker的numTaskCacheLevels配置有关。该操作之所以放到Job的初始化中完成,主要是为了提高系统不过效率,因为集群中的绝大多数Job中的Map任务都是在等待被调度给TaskTracker节点执行的,不可能说在Job被第一次调度时再执行这个操作。但这里的关键问题是如何对作业中的每一个Map任务进行预分配的。举个例子,JobTracker的numTaskCacheLevels配置为2,假设一个Map任务对应的输入数据片有三个副本,分别存储在在数据节点/network1/rack1/datanode1、/network1/rack1/datanode2、/network1/rack2/datanode4上,那么把这个Map任务分配给在网络拓扑路径在/network1/rack1/datanode1、/network1/rack1/datanode2、/network1/rack2/datanode4、/network1/rack1/、/network1/rack2/上的TaskTracker节点时就称作是给这个TaskTracker节点分配了一个本地任务;反之,如果这个Map任务被分配给了不在上面拓扑路径下的TaskTracker节点的话,就说给这个TaskTracker节点分配了一个非本地任务。另外,numTaskCacheLevels的值可以通过配置文件的mapred.task.cache.levels项来设置,而且,当TaskTracker节点向JobTracker节点注册时,如果这个在TaskTracker节点在解析其拓扑路径后,其层次数小于numTaskCacheLevels的话,JobTracker节点将会自动停止一切服务。
- Job的初始化—EagerTaskInitializationListener
- Job 提交&初始化
- hadoop job初始化源码浅析
- Job的提交—客户端
- Job的提交—客户端
- FairScheduler job初始化过程源码浅析
- Hadoop源码解读-Job初始化过程
- Hadoop源码解读-Job初始化过程
- Elastic-Job-Lite 源码阅读 ---- 任务初始化
- Job的提交——JobTracker
- Job的提交——JobTracker
- Quartz——有状态的job和无状态的job
- Quartz——有状态的job和无状态的job
- 【niubi-job——一个分布式的任务调度框架】----niubi-job这下更牛逼了!
- Jenkins的新建job和配置job
- Jenkins的新建job和配置job
- JOB JOB JOB——优先队列 + 贪心
- [Oracle]job的建立
- 内部类和匿名内部类
- Java解析XML文件
- 真正开始的it之路
- 关于内存对齐问题(二)
- 今天遇到foreach错误和技巧小结。
- Job的初始化—EagerTaskInitializationListener
- hadoop配置文件详解、安装及相关操作
- java 使用CharsetDetector检测文件的编码方式
- 控制文件的备份与恢复
- QuickContact分析及其弹出窗口实现
- LPC1768系统方框图
- MVC模式
- vi编辑代码无法语法着色的问题
- String convert int , and int convert String