hadoop提交作业分析Word格式.docx
《hadoop提交作业分析Word格式.docx》由会员分享,可在线阅读,更多相关《hadoop提交作业分析Word格式.docx(16页珍藏版)》请在冰豆网上搜索。
"
];
then
#echo"
runwithheapsize$HADOOP_HEAPSIZE"
JAVA_HEAP_MAX="
-Xmx"
m"
#echo$JAVA_HEAP_MAX
fi
首先赋予默认值-Xmx1000m,然后检查中是否设置并导出了HADOOP_HEAPSIZE,如果有的话,就使用该值覆盖,得到最后的JAVA_HEAP_MAX。
接着是分析CLASSPATH,这是这个脚本的重点之一。
这部分主要就是添加了相应依赖库和配置文件到CLASSPATH。
#首先用Hadoop的配置文件目录初始化CLASSPATH
CLASSPATH="
${HADOOP_CONF_DIR}"
……
#下面是针对于Hadoop发行版,添加Hadoop核心Jar包和webapps到CLASSPATH
if[-d"
$HADOOP_HOME/webapps"
CLASSPATH=${CLASSPATH}:
$HADOOP_HOME
fi
forfin$HADOOP_HOME/hadoop-*;
do
$f;
done
#添加libs里的Jar包
forfin$HADOOP_HOME/lib/*.jar;
Done
#下面的TOOL_PATH只在命令为“archive”时才添加到CLASSPATH
TOOL_PATH=${TOOL_PATH}:
forfin$HADOOP_HOME/build/hadoop-*;
#最后添加用户的自定义HadoopClasspath
$HADOOP_CLASSPATH"
${HADOOP_CLASSPATH}
上面只分析一部分,由于代码比较长,针对开发者部分的CLASSPATH添加没有列出来。
下面是这个脚本的重点、实体之处:
CLASS分析。
Shell脚本会根据你输入的命令参数来设置CLASS和HADOOP_OPTS,其中CLASS所指向的类才是最终真正执行你的命令的实体。
#figureoutwhichclasstorun
$COMMAND"
namenode"
];
CLASS='
'
HADOOP_OPTS="
$HADOOP_OPTS$HADOOP_NAMENODE_OPTS"
elif["
fs"
CLASS=
$HADOOP_OPTS$HADOOP_CLIENT_OPTS"
jar"
archive"
${TOOL_PATH}
else
CLASS=$COMMAND
这里我们要关心的就是"
="
时对应的类,这个类等下我们继续分析,这是我们通向最终目标的下一个路口。
脚本在最后还设置了、等HADOOP_OPTS。
接着,就利用exec命令带上刚才的参数提交任务了。
通过对上面的分析,我们知道了,如果想取代这个脚本,那就必须至少把Hadoop依赖的库和配置文件目录给加到CLASSPATH中(JAVA_HEAP_MAX和HADOOP_OPTS不是必须的),然后调用类来提交Jar到Hadoop。
PS:
对BashShell不熟的可以先看看这我们分析了bin/hadoop脚本,知道了提交一个Hadoop作业所需要的基本设置以及真正执行任务提交的类。
这一篇我们就来分析这个提交任务的类,看它内部具体又做了些什么。
RunJar是Hadoop中的一个工具类,结构很简单,只有两个方法:
main和unJar。
我们从main开始一步步分析。
main首先检查传递参数是否符合要求,然后从第一个传递参数中获取jar包的名字,并试图从jar中包中获取manifest信息,以查找mainclassname。
如果查找不到mainclassname,则把传递参数中的第二个设为mainclassname。
接下去,就是在"
下创建一个临时文件夹,并挂载上关闭删除线程。
这个临时文件夹用来放置解压后的jar包内容。
jar包的解压工作由unJar方法完成,通过JarEntry逐个获取jar包内的内容,包括文件夹和文件,然后释放到临时文件夹中。
解压完毕后,开始做classpath的添加,依次把解压临时文件夹、传递进来的jar包、临时文件夹内的classes文件夹和lib里的所有jar包加入到classpath中。
接着以这个classpath为搜索URL新建了一个URLClassLoader(要注意这个类加载器的parent包括了刚才bin/hadoop脚本提交时给的classpath),并设置为当前线程的上下文类加载器。
最后,利用方法,以刚才的那个URLClassLoader为类加载器,动态生成一个mainclass的Class对象,并获取它的main方法,然后以传递参数中剩下的参数作为调用参数来调用这个main方法。
好了,从上分析看来,这个RunJar类是一个很简单的类,就是解压传递进来的jar包,再添加一些classpath,然后动态调用jar包里的mainclass的main方法。
看到这里,我想你应该知道如何利用java代码来编写一个替代bin/hadoop的程序了,主要就是两步:
添加Hadoop的依赖库和配置文件;
解压jar包,再添加一些classpath,并动态调用相应方法。
最偷懒的方法,直接用RunJar类就行了。
通过前面两篇文章的分析,对Hadoop的作业提交流程基本明了了,下面我们就可以开始编写代码模拟这个流程。
第一步要做的是添加Hadoop的依赖库和配置文件到classpath。
最常用的方法就是用一个容器先把各个要添加到classpath的文件或文件夹存储起来,后面再作为类加载器的URL搜索路径。
/**
*Addadirectoryorfiletoclasspath.
*
*@paramcomponent
*/
publicstaticvoidaddClasspath(Stringcomponent){
if((component!
=null)&
&
()>
0)){
try{
Filef=newFile(component);
if()){
URLkey=().toURL();
if(!
(key)){
(key);
}
}catch(IOExceptione){
}
上面的classPath变量就是我们声明用来装载classpath组件的容器。
privatestaticArrayList<
URL>
classPath=newArrayList<
();
由于需要添加一些文件夹下的所有Jar包,所以我们还要实现一个遍历添加某文件夹下文件的方法。
*Addalljarsindirectorytoclasspath,sub-directoryisexcluded.
*@paramdirPath
publicstaticvoidaddJarsInDir(StringdirPath){
Filedir=newFile(dirPath);
()){
return;
File[]files=();
if(files==null){
for(inti=0;
i<
;
i++){
if(files[i].isDirectory()){
continue;
}else{
addClasspath(files[i].getAbsolutePath());
简单起见,这个方法没有使用Filter,对文件夹下的文件是通吃,也忽略掉了子文件夹,只处理根文件夹。
好了,有了基础方法,下面就是照着bin/hadoop中脚本所做的,把相应classpath添加进去。
*Adddefaultclasspathlistedinbin/hadoopbash.
*@paramhadoopHome
publicstaticvoidaddDefaultClasspath(StringhadoopHome){
addClasspath(hadoopHome+"
/conf"
);
/build/classes"
if(newFile(hadoopHome+"
/build/webapps"
).exists()){
/build"
/build/test/classes"
/build/tools"
/webapps"
addClasspath(hadoopHome);
addJarsInDir(hadoopHome);
addJarsInDir(hadoopHome+"
/lib"
/lib/"
/build/ivy/lib/Hadoop/common"
至此,该添加classpath的都已添加好了(未包括第三方库,第三方库可用Conf中的tmpjars属性添加。
),下去就是调用RunJar类了。
本文为了方便,把RunJar中的两个方法提取了出来,去掉了一些可不要的Hadoop库依赖,然后整合到了类EJob里。
主要改变是把原来解压Jar包的“文件夹改为"
,并提取出了fullyDelete方法。
利用这个类来提交Hadoop作业很简单,下面是一个示例:
args=newString[4];
args[0]="
E:
\\Research\\Hadoop\\"
;
args[1]="
pi"
args[2]="
2"
args[3]="
100"
armainclassargs命令类似,但是忽略掉了bin/hadoopjar这个命令,因为我们现在不需要这个脚本来提交作业了。
新建一个Project,添加一个class,在main里粘上上面的代码,然后RunasJavaApplication。
注意看你的Console,你会发现你已经成功把作业提交到Hadoop上去了。
有图有真相,粘一下我的运行示例(在Win上开Eclipse,HadoopCluster在Linux,配置文件同Cluster的一样)。
下面是在ClouderaDesktop看到的Job信息(它的时间是UTC的)。
用上述方法,我们可以做一个类似ClouderaDesktop的Web应用,接受用户提交的Jar,并在Action处理中提交到Hadoop中去运行,然后把结果返回给用户。
由于篇幅原因,加上前面介绍过RunJar类,所本文没有粘关于RunJar类的代码,不过你放心,本文提供例子工程下载。
你可以在此基础上优化它,添加更多功能。
由于大部分是Hadoop的代码,So,该代码基于ApacheLicense。
-->
>
点我下载<
<
--
到此,以Java方式提交Hadoop作业介绍完毕。
但,是否还可以再进一步呢现在还只能提交打包好的MR程序,尚不能像HadoopEclipsePlugin那样能直接对包含Mapper和Reducer的类RunonHadoop。
为什么直接对这些类RunasJavaApplication提交的作业是在Local运行的呢这其中又包含有什么秘密呢我们将在下面的文章中更深入的剖析Hadoop的作业提交代码,去到最底层,慢慢揭开它的黑面纱。
前面我们所分析的部分其实只是Hadoop作业提交的前奏曲,真正的作业提交代码是在MR程序的main里,RunJar在最后会动态调用这个main,在
(二)里有说明。
我们下面要做的就是要比RunJar更进一步,让作业提交能在编码时就可实现,就像HadoopEclipsePlugin那样可以对包含Mapper和Reducer的MR类直接RunonHadoop。
一般来说,每个MR程序都会有这么一段类似的作业提交代码,这里拿WordCount的举例:
Configurationconf=newConfiguration();
String[]otherArgs=newGenericOptionsParser(conf,args).getRemainingArgs();
if!
=2){
Usage:
wordcount<
in>
<
out>
(2);
Jobjob=newJob(conf,"
wordcount"
(job,newPath(otherArgs[0]));
(job,newPath(otherArgs[1]));
(true)0:
1);
首先要做的是构建一个Configuration对象,并进行参数解析。
接着构建提交作业用的Job对象,并设置作业Jar包、对应Mapper和Reducer类、输入输出的Key和Value的类及作业的输入和输出路径,最后就是提交作业并等待作业结束。
这些只是比较基本的设置参数,实际还支持更多的设置参数,这里就不一一介绍,详细的可参考API文档。
一般分析代码都从开始一步步分析,但我们的重点是分析提交过程中发生的事,这里我们先不理前面的设置对后面作业的影响,我们直接跳到作业提交那一步进行分析,当碰到问题需要分析前面的代码时我会再分析。
当调用时,其内部调用的是submit方法来提交,如果传入参数为ture则及时打印作业运作信息,否则只是等待作业结束。
submit方法进去后,还有一层,里面用到了job对象内部的jobClient对象的submitJobInternal来提交作业,从这个方法才开始做正事。
进去第一件事就是获取jobId,用到了jobSubmitClient对象,jobSubmitClient对应的类是JobSubmissionProtocol的实现之一(目前有两个实现,JobTracker和LocalJobRunner),由此可判断出jobSubmitClient对应的类要么是JobTracker,要么是LocalJobRunner。
呃,这下有点想法了,作业提交是上到JobTracker去,还是在本地执行可能就是看这个jobSunmitClient初始化时得到的是哪个类的实例了,我们可以稍稍的先往后看看,你会发现submitJobInternal最后用了(jobId)来提交作业,再稍稍看看JobTracker和LocalJobRunner的submitJob实现,看来确实是这么回事。
好,那我们就先跳回去看看这个jobSubmitClient是如何初始化的。
在JobClient的init中我们可以发现jobSubmitClient的初始化语句:
Stringtracker=("
"
local"
if("
.equals(tracker)){
=newLocalJobRunner(conf);
=createRPCProxy(conf),conf);
哈,跟conf中的属性有关,如果你没设置,那默认得到的值就是local,jobSubmitClient也就会被赋予LocalJobRunner的实例。
平时,我们开发时一般都只是引用lib里面的库,不引用conf文件夹里的配置文件,这里就能解释为什么我们直接RunasJavaApplication时,作业被提交到Local去运行了,而不是HadoopCluster中。
那我们把conf文件夹添加到classpath,就能RunonHadoop了么目前下结论尚早,我们继续分析(你添加了conf文件夹后,可以提交试一试,会爆出一个很明显的让你知道还差什么的错误,这里我就卖卖官子,先不说)。
jobId获取到后,在SystemDir基础上加jobId构建了提交作业的目录submitJobDir,SystemDir由JobClient的getSystemDir方法得出,这个SystemDir在构建fs对象时很重要,确定了返回的fs的类型。
下去的configureCommandLineOptions方法主要是把作业依赖的第三方库或文件上传到fs中,并做classpath映射或Symlink,以及一些参数设置,都是些细微活,这里不仔细分析。
我们主要关心里面的两个地方,一个是:
FileSystemfs=getFs();
看上去很简单,一句话,就是获取FileSystem的实例,但其实里面绕来绕去,有点头晕。
因为Hadoop对文件系统进行了抽象,所以这里获得fs实例的类型决定了你是在hdfs上操作还是在localfs上操作。
好了,我们冲进去看看。
publicsynchronizedFileSystemgetFs()throwsIOException{
if==null){
PathsysDir=getSystemDir();
=(getConf());
returnfs;
看见了吧,fs是由sysDir的getFileSystem返回的。
我们再冲,由于篇幅,下面就只列出主要涉及的语句。
(),conf);
↓
(uri,conf);
fs=createFileSystem(uri,conf);
Class<
clazz=("
fs."
+()+"
.impl"
null);
if(clazz==null){
thrownewIOException("
NoFileSystemforscheme:
"
+());
FileSystemfs=(FileSystem)(clazz,conf);
又是跟conf有关,看来conf是得实时跟住的。
这里用到了Java的反射技术,用来动态生成相应的类实例。
其中的class获取与有密切关系,而uri就是在刚才的sysDir基础上构成,sysDir的值又最终是由jobSubmitClient的实例决定的。
如果jobSubmitClient是JobTracker的实例,那Scheme就是hdfs。
如果是LocalJobRunner的实例,那就是file。
从你可以找到如下的信息:
property>
name>
value>
description>
TheFileSystemforfile:
uris.<
/description>
/property>
TheFileSystemforhdfs:
所以在前面的作业提交代码中,在初始化Job实例时,很多事已经决定了,由conf文件夹中的配置文件决定。
Configuration是通过当前线程上下文的类加载器来加载类和资源文件的,所以要想RunonHadoop,第一步必须要让Conf文件夹进入Configuration的类加载器的搜索路径中,也就是当前线程上下文的类加载器。
第二个要注意的地方是:
StringoriginalJarPath=
if(o