Hama框架学习(二) Master如何将job规划到GroomServer
来源:互联网 发布:mac下python开发环境 编辑:程序博客网 时间:2024/05/20 14:19
*作者:王连平
如有转载,请注明文章出处:*http://blog.csdn.net/wlp001007/article/details/45057463
在上一篇学习文章中,已经讲到,BSPJobClient对象的launchJob()函数已经获取的了BSPMaster的远程对象代理,并且调用了BSPMaster的submitJob()这个函数,本文章将详细讲解BSPMaster如何将一个Job进行提交的。实际上,BSPMaster提交Job的过程也是如何将Job分配给GroomServer的过程,我们任然从源码的角度来分析。
一、 首先来看BSPMaster这个类的继承关系。
从上面可以看出,BSPMaster继承了五个类,也就意味着BSPMaster在整个框架中扮演了多个角色。其他先不看,会发现BSPMaster继承了JobSubmissionProtocol类,这也是《Hama框架学习从源码角度分析job的提交和运行过程(一)》中介绍过的,BSPJobClient这是个工作提交的客户端类,它提交给远端的BSPMaster的方法就是获取BSPMaster的远程代理,然后调用BSPMaster的submitJob()方法。下面来看submitJob()方法。
public JobStatus submitJob(BSPJobID jobID, String jobFile) throws IOException { LOG.info("这里是BSPMaster.submit"); if (jobs.containsKey(jobID)) { // job already running, don't start twice LOG.info("The job (" + jobID + ") was already submitted"); return jobs.get(jobID).getStatus(); } JobInProgress job = new JobInProgress(jobID, new Path(jobFile), this, this.conf); ++totalSubmissions; if (LOG.isDebugEnabled()) { LOG.debug("Submitting job number = " + totalSubmissions + " id = " + job.getJobID()); } return addJob(jobID, job); }
从该函数可以看到,在BSPMaster中有个Map <BSPJobID, JobInProgress >
类型的jobs,它存储了当前系统中所有的Job,这些job的类型是JobInProgress,因此,该函数首先进行判断,是否已经存在该job,如果不存在则新创建JobInProgress对象,同时将提交总数totalSubmissions自增加。最后再看返回值,return addJob(jobID, job);暂且把该返回值放下,先去先去查看下JobInProgress这个类的创建,看看到底里面做了些什么工作。
public JobInProgress(BSPJobID jobId, Path jobFile, BSPMaster master, Configuration conf) throws IOException { this.conf = conf; this.jobId = jobId; this.localFs = FileSystem.getLocal(conf); //获得本地的文件系统,也就是conf中配置好的tmpdir等目录 this.jobFile = jobFile; //设置job的文件目录 this.master = master; //设置job的master this.status = new JobStatus(jobId, null, 0L, 0L, //初始化job的状态 JobStatus.State.PREP.value(), counters); this.startTime = System.currentTimeMillis(); //设置本job的开始时间,该值为系统时间 this.superstepCounter = 0; //本job的超级步计数 this.restartCount = 0; //重启计数 this.localJobFile = master.getLocalPath(BSPMaster.SUBDIR + "/" + jobId + ".xml"); //从master中获取将要存放该工作xml文件的本地目录 this.localJarFile = master.getLocalPath(BSPMaster.SUBDIR + "/" + jobId + ".jar"); //从master中获取将要存放该工作jar文件的本地目录 Path jobDir = master.getSystemDirectoryForJob(jobId); //从master中获得文件系统中该工作的路径,实际上是dfs上该job的文件目录 FileSystem fs = jobDir.getFileSystem(conf); fs.copyToLocalFile(jobFile, localJobFile); //将该job 的文件拷贝到本地的目录中 BSPJob job = new BSPJob(jobId, localJobFile.toString()); //创建新的BSPJob this.jobSplit = job.getConfiguration().get("bsp.job.split.file"); //获取job的split this.numBSPTasks = job.getNumBspTask(); //获取job要求的bsp的task数 this.taskCompletionEvents = new ArrayList<TaskCompletionEvent>( numBSPTasks + 10); this.maxTaskAttempts = job.getConfiguration().getInt( Constants.MAX_TASK_ATTEMPTS, Constants.DEFAULT_MAX_TASK_ATTEMPTS); this.profile = new JobProfile(job.getUser(), jobId, jobFile.toString(), job.getJobName()); this.setJobName(job.getJobName()); //设置一下job的名字 status.setUsername(job.getUser()); //设置用户名 status.setStartTime(startTime); //设置开始时间 String jarFile = job.getJar(); //设置jar文件目录 if (jarFile != null) { fs.copyToLocalFile(new Path(jarFile), localJarFile); //将文件从hdfs上拷贝到master的本地目录 } failedTasksTillNow = new HashSet<Task>(2 * tasks.length); //失败task }
从上面的代码中可以看出,创建的JobInProgress类还是丰富了job的很多内容,其实不止是这些,在这个类中还有一个重要的方法—initTasks(),这个方法完成了将一个job划分成多个task的工作,详细内容后面再讲解。看完该方法,我们回到BSPMaster的submitJob()方法中,该方法的返回语句为return addJob(jobID, job),因此,我们要查看addJob()这个方法,如下:
/** * Adds a job to the bsp master. Make sure that the checks are inplace before * adding a job. This is the core job submission logic * 该方法主要是将一个job添加到bsp master中去 * @param jobId The id for the job submitted which needs to be added */ private synchronized JobStatus addJob(BSPJobID jobId, JobInProgress job) { synchronized (jobs) { jobs.put(job.getProfile().getJobID(), job); //向jobs中添加新创建号的JobInProgress对象 for (JobInProgressListener listener : jobInProgressListeners) { try { listener.jobAdded(job);//添加到相应的队列中 } catch (IOException ioe) { LOG.error("Fail to alter Scheduler a job is added.", ioe); } } } return job.getStatus(); }
上述代码中,发现,在BSPMaster中存在一个jobInProgressListeners,这是个List类型的对象,存放了多个job监听器在有新的job加入时将job添加到相应的队列。那么问题来了,这个队列到底在哪,是谁的对象?我们不妨顺着listener.jobAdded(job)这句代码找下去。首先,listener是JobInProgressListener的一个实例,而JobListener又继承了JobInProgressListener,如下图所示
所以,其实listener是JobListener一个类,查看JobListener类可以发现,这个类是SimpleTaskScheduler的一个内部类,我们暂且不说为什么是内部类,先看下具体的将job添加到哪里去了。查看JobListener的jobAdded()方法如下:
public void jobAdded(JobInProgress job) throws IOException { LOG.info("这里是JobListener.jobAdded."); queueManager.get().initJob(job); // init task 初始化job queueManager.get().addJob(WAIT_QUEUE, job);//将任务加到等待队列,随后由相应的进程从中取出Job来运行 }
可以看出来,该方法是将job添加到了WAIT_QUEUE类型的队列中去了,我们暂且记住这个结果,同时该方法中的队列管理者queueManager是SimpleTaskScheduler的一个内部成员对象。同时,从上面的代码中可以看到,在将该JobInProgress对象添加到队列之前,将该对象进行了初始化的操作,这也是前面我提到的JobInProgress.initTasks()方法。该方法可不简单,完成了工作分配中的很重要的一步,即将Job划分成多个task。下面来看看这个方法,源码如下:
// /////////////////////////////////////////////////// // Create/manage tasks 创建并管理job的task // /////////////////////////////////////////////////// public synchronized void initTasks() throws IOException { if (tasksInited) { return; } Path sysDir = new Path(this.master.getSystemDir()); //获得系统文件目录 FileSystem fs = sysDir.getFileSystem(conf); if (jobSplit != null) { DataInputStream splitFile = fs.open(new Path(this.jobSplit)); BSPJobClient.RawSplit[] splits; try { splits = BSPJobClient.readSplitFile(splitFile); //读入job输入文件的分片信息,splitFile 该文件记录了 //每一个part所包含的所有block,以及每个block所属的PartionID,所在hdfs的位置 } finally { splitFile.close(); } numBSPTasks = splits.length; //一般的,有多少个输入block就要启动多少个任务(BSPPeer),这个block怎么理解, //我们每个partion可能超过64M,因此可能分成多个block,因此当我们的分区太大就会导致每个分区启动多个task, //因此,我们一般将输入分区控制在64M之内,这样保证每个分区启动一个task LOG.info("num BSPTasks: " + numBSPTasks); // adjust number of BSP tasks to actual number of splits this.tasks = new TaskInProgress[numBSPTasks]; //初始化tasks,这是个数组,说明一个问题就是一个job会启动多个task, //分不到不同的GroomServer上去 for (int i = 0; i < numBSPTasks; i++) { //此处将split[i]分配到具体的task中去,每个split中包含了一个bolock的路径、主机、 //位置、数据offset和length等信息 tasks[i] = new TaskInProgress(getJobID(), this.jobFile.toString(), splits[i], this.conf, this, i); } } else { this.tasks = new TaskInProgress[numBSPTasks]; for (int i = 0; i < numBSPTasks; i++) { tasks[i] = new TaskInProgress(getJobID(), this.jobFile.toString(), null, this.conf, this, i); } } this.taskToGroomMap = new HashMap<Task, GroomServerStatus>(2 * tasks.length); //开启的任务映射到GroomServer上去 this.taskCountInGroomMap = new HashMap<GroomServerStatus, Integer>(); //在每个GroomServer中任务的数量 this.recoveryTasks = new HashSet<TaskInProgress>(2 * tasks.length); // Update job status this.status = new JobStatus(this.status.getJobID(), this.profile.getUser(), 0L, 0L, JobStatus.RUNNING, counters); // delete all nodes belonging to that job before start MasterSyncClient syncClient = master.getSyncClient(); syncClient.registerJob(this.getJobID().toString()); tasksInited = true; Class<?> taskAllocatorClass = conf.getClass(Constants.TASK_ALLOCATOR_CLASS, //初始化本JOB的任务划分策略 BestEffortDataLocalTaskAllocator.class, TaskAllocationStrategy.class); this.taskAllocationStrategy = (TaskAllocationStrategy) ReflectionUtils .newInstance(taskAllocatorClass, new Object[0]); if (conf.getBoolean(Constants.FAULT_TOLERANCE_FLAG, false)) { Class<?> ftClass = conf.getClass(Constants.FAULT_TOLERANCE_CLASS, AsyncRcvdMsgCheckpointImpl.class, BSPFaultTolerantService.class); if (ftClass != null) { try { faultToleranceService = ((BSPFaultTolerantService<?>) ReflectionUtils .newInstance(ftClass, new Object[0])) .constructMasterFaultTolerance(jobId, maxTaskAttempts, tasks, conf, master.getSyncClient(), taskAllocationStrategy); LOG.info("Initialized fault tolerance service with " + ftClass.getCanonicalName()); } catch (Exception e) { throw new IOException(e); } } } LOG.info("Job is initialized."); }
好了,到这里,一个Job的初始化工作算是完成了,接着上面那一段继续往下看,上面说到当把Job初始化后就将它加入到了一个名叫WAIT_QUEUE类型的队列中去了。下面该讲一下这个队列到底是怎么回事了,而且我们直观猜测,仅仅加入到这个队列中是不行的,还要把它真正的取出来规划安排到具体的Groomserver上去才行。这时就要说一下BSPMaster类与TaskScheduler这两个类了。
BSPMaster类不用多说,是作为Master来为整个hama集群服务的;TaskScheduler从名字上就可以看出,这是个任务规划器类,是为Job规划任务的。在BSPMaster这个类中,包含了TaskScheduler的对象实例,如下定义:
private TaskScheduler taskScheduler;// Create the scheduler and init scheduler services Class<? extends TaskScheduler> schedulerClass = conf.getClass( "bsp.master.taskscheduler", SimpleTaskScheduler.class, TaskScheduler.class); this.taskScheduler = ReflectionUtils.newInstance(schedulerClass, conf);
上面的代码可以看到BSPMaster这个类中的成员变量taskScheduler的定义和初始化,初始化时默认采用的是SimpleTaskScheduler.class 这个类在前面已经提到—BSPMaster最后将初始化好的Job放入了JobListener去了,而JobListener是SimpleTaskScheduler的一个内部类,实际上JobListener将Job放入SimpleTaskScheduler的成员对象—一个队列中去了。仔细分析发现,BSPMaster和SimpleTaskScheduler这两个类有着密切的联系。简单的关系图如下:
首先,继承关系不用说了,说一下蓝色线和红色线的关系,蓝色线表示的是“是实例”的关系,比如BSPMaster.taskScheduler是SimpleTaskScheduler的一个实例对象,SimpleTaskScheduler.groomServermanager是BSPMaster的一个实例对象;红色线表示的是BSPMaster的List对象中的成员类型是SimpleTaskScheduler中的jobListener。上面的这个关系图只是说明了一下实例对象的类型关系,但更重要的一点是在Hama框架运行时肯定要产生一个BSPMaster这个对象,当然作为内部对象也要产生一个TaskScheduler对象,而TaskScheduler中的groomServermanager等几个成员所赋的值恰恰是BSPMaster这个实例对象。什么意思呢,taskScheduler不仅是BSPMaster成员对象,他也将BSPMaster这个实例作为自己的成员对象来使用,这样一来,这两个类就可以相互操作对方的函数或者成员对象了。等下再具体的源码中就可以看到了。
上文中只是提到了放入队列,那么什么时候真正取出来进行规划呢?其实,我们很容易想到一个编程模型“生产与消费者”模型,生成者我们已经找到了,消费者还没有出现。从另外一个角度思考,当启动Hama时,我们要运行BSPMasterRunner这个带有main方法的主类,在该住方法中会启动一系列的服务,当然会启动消费者,后面的内容在后边的文章中讲解。
- Hama框架学习(二) Master如何将job规划到GroomServer
- Hama框架学习(三) Master如何将job规划到GroomServer
- hama学习笔记(2)-在eclipse中编译hama源码、写hama job
- Hama框架学习(一) 从源码角度分析job的提交和运行过程
- Hama学习笔记(6)-获取各个peer(task)的信息、确定master task
- hama学习笔记(1)-配置和启动hama
- BSP-Apache.HAMA运行过程(框架)
- IDEA如何将master合并到指定支代码
- IDEA如何将master合并到指定支代码
- Quartz.NET(作业调度框架) 学习笔记(二)【Hello Job】
- Hama学习总结
- 如何将MIDlet应用移植到BlackBerry(二)
- WebMagic爬虫框架及javaEE SSH框架将数据保存到数据库(二)
- Hama学习笔记(3)-编写BSP程序
- Hama学习笔记(4)-消息的发送与存储
- hama学习笔记(6)-peers之间通信速度测试
- ObjectARX学习笔记(二十七)---如何拷贝将一个AcDbDatabase拷贝到另一张dwg里面
- HAMA
- Eclipse C/C++开发调试环境安装记录
- GOOGLE与百度经纬度互转(plsql版)
- Iplimage versus Mat
- 数论基础讲解
- A/B测试终极指南
- Hama框架学习(二) Master如何将job规划到GroomServer
- linux 命令笔记
- web消息推送技术
- 最短路专题
- Java学习笔记之泛型
- E洗车高调推399保养,重组汽车后市场格局
- oracle sql操作xml
- 高斯消元专题
- HTML 5 学习笔记