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属性被使用了,设置MapperClass和ReducerClass,并进行一些确认,确认某些属性没有被设置
setUseNewAPI();
//初始化cluster对象
connect();
//根据初始化得到的cluster对象生成JobSubmitter对象
final JobSubmitter submitter = getJobSubmitter(cluster.getFileSystem(),cluster.getClient());
//ugi是UserGroupInformation类的实例,表示Hadoop中的用户和组信息,这个类包装了一个JAAS Subject以及提供了方法来确定用户的名字和组,它同时支持Windows、Unix和Kerberos登录模块
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接口,用来进行JobClient和JobTracker之间的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进行通信