job.waitForCompletion的具体流程

Job运行是通过job.waitForCompletion(true),true表示将运行进度等信息及时输出给用户,false的话只是等待作业结束

Job对象有两种状态:DEFINE和RUNNING,是通过JobState枚举类定义的

public static enum JobState {DEFINE,RUNNING}

当一个对象创建时,state状态被声明为DEFINE

privateJobStatestate = JobState.DEFINE

当且仅当Job对象处于DEFINE状态,才可以用来设置作业的一些配置,如ReduceTask的数量,InputFormat类,Mapper类,Reducer类等,当Job被submit之后,Job对象的状态就为RUNNING,这时候就无法设置作业的配置,作业处于调度运行阶段,处于RUNNING状态的Job可以获取map task和reduce task的进度

public boolean waitForCompletion(boolean verbose )throws IOException, InterruptedException,

                                           ClassNotFoundException {

    if (state == JobState.DEFINE) {

      submit();

    }

if (verbose) {

//监控一个Job对象以及实时打印进程的状态

      monitorAndPrintJob();

    } else {

      // get the completion poll interval from the client.

      int completionPollIntervalMillis =

        Job.getCompletionPollInterval(cluster.getConf());

//进入循环,以一定的时间间隔检查所提交的job是否执行完成

      while (!isComplete()) {

        try {

          Thread.sleep(completionPollIntervalMillis);

        } catch (InterruptedException ie) {

        }

      }

    }

    return isSuccessful();

  }

提交job到集群中

  public void submit() throws IOException, InterruptedException,ClassNotFoundException {

    //确保job的状态是DIFINE

ensureState(JobState.DEFINE);

//默认使用new APIs,除非他们被显式设置或者旧的mapper或者reduce属性被使用了,设置MapperClassReducerClass,并进行一些确认,确认某些属性没有被设置

setUseNewAPI();

//初始化cluster对象

connect();

//根据初始化得到的cluster对象生成JobSubmitter对象

final JobSubmitter submitter = getJobSubmitter(cluster.getFileSystem(),cluster.getClient());

//ugiUserGroupInformation类的实例,表示Hadoop中的用户和组信息,这个类包装了一个JAAS Subject以及提供了方法来确定用户的名字和组,它同时支持WindowsUnixKerberos登录模块

Job对象的submit()方法,具体实现是调用JobSubmitter对象的submitJobInternal方法

    status = ugi.doAs(new PrivilegedExceptionAction<JobStatus>(){

      public JobStatus run()throws IOException, InterruptedException,

      ClassNotFoundException {

        return submitter.submitJobInternal(Job.this,cluster);

      }

});

//job的状态设置为RUNNING

    state = JobState.RUNNING;

    LOG.info("The url to track the job: " + getTrackingURL());

   }

 

JobStatussubmitJobInternal(Jobjob, Cluster cluster)throwsClassNotFoundException, InterruptedException, IOException()方法主要是用来进行以下步骤

作业提交工程包括:

检查job的输入输出规范

计算job的InputSplit

如果需要的话,设置需要的核算信息对于job的分布式缓存

复制job的jar和配置文件到分布式文件系统的系统目录

提交作业执行以及监控它的状态

submitClient的类型是ClientProtocol接口,用来进行JobClientJobTracker之间的RPC通信,JobClient可以利用类提供的方法来提交一个作业,也可以获取当前系统的信息

首先调用checkSpecs(job),检查job的输出空间

addMRFrameworkToDistributedCache(conf);将MapReduce框架加入分布式缓存中

PathjobStagingArea = JobSubmissionFiles.getStagingDir(cluster, conf);初始化job的工作根目录并返回path路径,同时跟踪所有必须的所有权和权限

InetAddressip = InetAddress.getLocalHost();得到本机的IP地址,如果有一个安全管理器,它的checkConnect方法会被调用并且以它的本机名和-l作为它的参数来确定这个操作是否被允许,如果操作不被允许,一个回环地址被返回

if(ip != null) {

      submitHostAddress = ip.getHostAddress();//返回IP地址字符串

      submitHostName = ip.getHostName();  //返回IP 地址的主机名;如果安全检查不允许操作,则返回IP 地址的文本表示形式

     conf.set(MRJobConfig.JOB_SUBMITHOST,submitHostName);

     conf.set(MRJobConfig.JOB_SUBMITHOSTADDR,submitHostAddress);

}

JobIDjobId = submitClient.getNewJobID() 为job分配一个名字

job.setJobID(jobId)设置job的Id

PathsubmitJobDir = new Path(jobStagingArea,jobId.toString()); 获得job的提交路径,也就是在jobStagingArea目录下建一个以jobId为文件名的目录

JobStatusstatus = null; 描述job目前的状态

//进行一系列的配置

conf.set(MRJobConfig.USER_NAME, UserGroupInformation.getCurrentUser().getShortUserName());

conf.set("hadoop.http.filter.initializers","org.apache.hadoop.yarn.server.webproxy.amfilter.AmFilterInitializer");

conf.set(MRJobConfig.MAPREDUCE_JOB_DIR, submitJobDir.toString());

TokenCache.obtainTokensForNamenodes(job.getCredentials(),new Path[] {submitJobDir }, conf); TokenCache这个类提供了面向用户的接口来进行从job client端到任务端的密钥传递,密钥只在job的提交前被存储以及在任务执行期间被读取,该方法是公共程序用来从namenode得到与传递的路径相关的委托标记

populateTokenCache(conf,job.getCredentials()) 得到密钥和标记,并将它们存储到TokenCache中

 

if (TokenCache.getShuffleSecretKey(job.getCredentials())==null) {

  KeyGenerator keyGen;

  try {

    keyGen = KeyGenerator.getInstance(SHUFFLE_KEYGEN_ALGORITHM);// 返回用HmacSHA1算法生成密钥的KeyGenerator 对象

    keyGen.init(SHUFFLE_KEY_LENGTH); //初始化此密钥生成器,64位

  } catch (NoSuchAlgorithmException e) {

    throw new IOException("Errorgenerating shuffle secret key", e);

  }

  SecretKey shuffleKey = keyGen.generateKey();  //生成一个密钥

  TokenCache.setShuffleSecretKey(shuffleKey.getEncoded(),job.getCredentials());  //返回基本编码格式的密钥,如果此密钥不支持编码,则返回 null,并将其赋值为job.getCredentials()

}

得到一个密钥来验证shuffle的传递

 

copyAndConfigureFiles(job,submitJobDir);  //这个方法实现文件上传

内部实现为:

  private void copyAndConfigureFiles(Job job, Path jobSubmitDir)

  throws IOException {

    Configuration conf =job.getConfiguration();

    short replication = (short)conf.getInt(Job.SUBMIT_REPLICATION, 10);

    copyAndConfigureFiles(job, jobSubmitDir,replication);

    // Set the working directory

    if (job.getWorkingDirectory() ==null) {

      job.setWorkingDirectory(jtFs.getWorkingDirectory());          

    }

  }

通过调用copyAndConfigureFiles(job, jobSubmitDir,replication);方法实现,这个方法首先获取用户在使用命令执行job的时候所指定的-libjars, -files, -archives文件,对应的conf配置参数是tmpfiles tmpjars tmparchives,这个过程是在ToolRunner.run()的时候进行解析的,当用户指定了这三个参数之后,会将这三个参数对应的文件都上传到hdfs上

用tmpfiles举例:将tmpfiles参数值按”,”分割,然后将每一个文件上传到hdfs,其中如果文件的路径本身就在hdfs中,那么将不进行上传操作,上传操作只针对文件不在hdfs中的文件。调用的方法是:Path newPath =copyRemoteFiles(fs,filesDir, tmp, job, replication),该方法内部使用的是FileUtil.copy(remoteFs, originalPath, jtFs, newPath, false, job)方法将文件上传至hdfs,注意此处的remoteFs和jtFs,remoteFs就是需上传文件的原始文件系统,jtFs则是jobTracker的文件系统(hdfs)。在文件上传至hdfs之后,会执行DistributedCache.createSymlink(job)这个方法,这个方法是创建一个别名,这里需要注意的是tmpfiles和tmparchives都会创建别名,而tmpjars则不会,个人认为tmpjars是jar文件,不是用户在job运行期间调用,所以不需要别名,而tmpfiles和tmparchives则在job运行期间用户可能会调用,所以使用别名可以方便用户调用

PathsubmitJobFile = JobSubmissionFiles.getJobConfPath(submitJobDir) 得到job conf的path

int maps =writeSplits(job, submitJobDir); 方法内部会根据我们之前的设置,选择使用new-api还是old-api分别进行分片操作

writeNewSplits(job,jobSubmitDir)  使用new-api进行操作,方法如下:

  int writeNewSplits(JobContext job, Path jobSubmitDir)throws IOException,

      InterruptedException,ClassNotFoundException {

    Configuration conf =job.getConfiguration();

    InputFormat<?, ?> input =

      ReflectionUtils.newInstance(job.getInputFormatClass(),conf);

 

    List<InputSplit> splits = input.getSplits(job);

    T[] array = (T[]) splits.toArray(new InputSplit[splits.size()]);

 

    // sort the splits into order based on size, so that thebiggest

    // go first

    Arrays.sort(array, new SplitComparator());

    JobSplitWriter.createSplitFiles(jobSubmitDir,conf,

        jobSubmitDir.getFileSystem(conf),array);

    return array.length;

  }

这个方法主要是根据我们设置的inputFormat.class通过反射获得inputFormat对象,然后调用inputFormat对象的getSplits方法,当获得分片信息之后调用JobSplitWriter.createSplitFiles方法将分片的信息写入到jobSubmitDir/job.split文件中

JobSplitWriter.createSplitFiles(jobSubmitDir,conf, jobSubmitDir.getFileSystem(conf), array);方法:

public static <T extends InputSplit>void createSplitFiles(Path jobSubmitDir,

      Configuration conf, FileSystem fs, T[]splits)

  throws IOException, InterruptedException {

    FSDataOutputStream out = createFile(fs,

        JobSubmissionFiles.getJobSplitFile(jobSubmitDir),conf);

    SplitMetaInfo[] info = writeNewSplits(conf,splits, out);

    out.close();

    writeJobSplitMetaInfo(fs,JobSubmissionFiles.getJobSplitMetaFile(jobSubmitDir),

        new FsPermission(JobSubmissionFiles.JOB_FILE_PERMISSION),splitVersion,

        info);

  }

内部调用writeNewSplits(conf, splits, out)进行写操作,该方法具体对每一个InputSplit对象进行序列化写入到输出流中,具体每个InputSplit对象写入的信息包括:split.getClass().getName(),serializer.serialize(split)将整个对象序列化。然后将InputSplit对象的locations信息放入SplitMetaInfo对象中,同时还包括InputSpilt元信息在job.split文件中的偏移量,该InputSplit的长度,以及SplitMetaInfo对象。然后调用writeJobSplitMetaInfo()方法将SplitMetaInfo对象写入jobSubmitDir/job.splitmetainfo文件中。writeJobSplitMetaInfo():将SplitMetaInfo对象写入jobSubmitDir/job.splitmetainfo文件中,具体写入的信息包括:JobSplit.META_SPLIT_FILE_HEADER,splitVersion,allSplitMetaInfo.length(SplitMetaInfo对象的个数,一个split对应一个SplitMetaInfo),然后分别将所有的SplitMetaInfo对象序列化到输出流中,到此文件的分片工作完成。

conf.setInt(MRJobConfig.NUM_MAPS,maps);  设置Map的数量

Stringqueue = conf.get(MRJobConfig.QUEUE_NAME,JobConf.DEFAULT_QUEUE_NAME);获取哪一个job正在被提交的队列的队列管理者

AccessControlListacl = submitClient.getQueueAdmins(queue);得到给定job队列的管理者

TokenCache.cleanUpTokenReferral(conf);  移除job标记参照在复制jobconf到HDFS之前由于tasks不需要这些设置,事实上,tasks会失败由于这些参照,因为现在的参照作为参照会使tasks指向一个不同的job

if(conf.getBoolean(

          MRJobConfig.JOB_TOKEN_TRACKING_IDS_ENABLED,

          MRJobConfig.DEFAULT_JOB_TOKEN_TRACKING_IDS_ENABLED)) {

        // Add HDFS tracking ids

        ArrayList<String> trackingIds = new ArrayList<String>();

        for (Token<?extends TokenIdentifier> t :

           job.getCredentials().getAllTokens()) {

         trackingIds.add(t.decodeIdentifier().getTrackingId());

        }

        conf.setStrings(MRJobConfig.JOB_TOKEN_TRACKING_IDS,

            trackingIds.toArray(new String[trackingIds.size()]));

      }

对trackingId的操作

printTokens(jobId,job.getCredentials());  打印log文件

writeConf(conf,submitJobFile); 将job文件写到submitJobFile中

status= submitClient.submitJob( jobId,submitJobDir.toString(), job.getCredentials());真正提交job,通过RPC进行通信

  • 11
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值