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方法的主类,在该住方法中会启动一系列的服务,当然会启动消费者,后面的内容在后边的文章中讲解。

2 0
原创粉丝点击