在eclipse中写mapreduce程序, 引用第三方jar文件, 可以利用eclipse hadoop插件直接run on hadoop提交, 很方便. 不过插件版本要和eclipse匹配, 不然总是local执行, 在50070是没有job产生的.
如果希望将程序发布成jar文件, 在namenode上通过命令行方式执行, 缺少了eclipse帮忙自动配置jar文件, 会遇到java.lang.ClassNotFoundException, 这个问题可分成两种情况讨论.
一. hadoop命令式如何执行的?
其实$HADOOP_HOME/bin/hadoop是一个脚本文件. 以下wordcount命令为例
bin/hadoop jar wordcount.jar myorg.WordCount /usr/wordcount/input /usr/wordcount/output
脚本文件解析参数, 配置类路径等, 最终执行的是如下命令:
exec java -classpath $CLASSPATH org.apache.hadoop.util.RunJar $@
其中$CLASSPATH : 包含${HADOOP_CONF_DIR}, $HADOOP_HOME下的*.jar以及$HADOOP_CLASSPATH;
p.s. hadoop脚本比较完整的分析可参见<Hadoop作业提交分析>.
有RunJar执行WordCount后, 就进入我们的程序了, 需要配置mapper, reducer以及输出输出路径等等, 最终通过执行job.waitForCompletion(true)向JobTracker提交这个作业.
到目前可知, 已经完成了本地执行部分, 如果这段时期发生ClassNotFoundException, 则可以在自己的脚本文件中配置$HADOOP_CLASSPATH, 包含需要的第三方jar文件, 再执行hadoop命令, 此为情况一.
二. JobTracker和TaskTracker如何获得第三方jar文件?
有时候提交job之后, 在map或者reduce函数中也会产生ClassNotFoundException. 这是因为map或reduce可能在其他机器上执行, 那些机器没有需要的jar文件, mapreduce作业交由JobTracker和TaskTracker执行, 两者如何获得第三方jar文件呢? 即为情况二.
我们首先来分析下mapreduce提交过程, 如下图所示.
step 1.和2. 通过Job类提交作业, 获得一个作业号, 并根据conf决定作业时提交给LocalJobRunner还是JobTracker
step 3. copy job resource
client将作业所需资源上传到hdfs上, 如job split, jar文件等. JobClient通过configureCommandLineOptions函数处理jar文件, 该方法中通过job获得这些参数内容
files = job.get("tmpfiles"); // 对应参数项-files libjars = job.get("tmpjars"); // 对应-libjars archives = job.get("tmparchives"); // 对应-archives
如果jar文件有配置, 则将其加入到分布式缓存DistributedCache中, -libjars为例:
if (libjars != null) { FileSystem.mkdirs(fs, libjarsDir, mapredSysPerms); String[] libjarsArr = libjars.split(","); for (String tmpjars: libjarsArr) { Path tmp = new Path(tmpjars); Path newPath = copyRemoteFiles(fs, libjarsDir, tmp, job, replication); DistributedCache.addArchiveToClassPath(newPath, job); } }
另外, 在mapreduce程序的配置中总是需要job.setJarByClass来指定运行的类, 如此hadoop就可以根据该class定位到所在的jar文件, 就是我们打包的jar, 将其上传到hdfs上. 到此jobClient完成了资源复制过程, 这些资源可供JobTracker和TaskTracker使用.
step4-10. JobClient提交job并执行作业(JobTracker以及TaskTracker工作就不展开了, 详见<Map-Reduce过程解析>).
三. 总结
p.s. 如果通过上面方法1.或2., 需要注意Configuration问题, 需要通过getConf()函数获得, 而不要自己new一个对象.
6 .在你的project里面建立一个lib文件夹,然后把所有的第三方jar包放到里面去,Hadoop会自动加载lib依赖里面的jar
<?xml version="1.0" encoding="UTF-8"?>
<project name="stat" default="job">
<property name="name" value="${Project.name}" />
<property name="version" value="0.1" />
<property name="build" value="build" />
<property name="build.classes" value="${build}/classes" />
<property name="build.unjar" value="${build}/unjar" />
<property name="dist" value="dist" />
<property name="src" value="src" />
<property name="lib" value="lib" />
<property name="conf" value="conf" />
<path id="project.class.path">
<fileset dir="${lib}">
<include name="*.jar" />
</fileset>
</path>
<target name="init">
<delete dir="${build}" />
<delete dir="${dist}" />
<mkdir dir="${build}" />
<mkdir dir="${build.classes}" />
<mkdir dir="${dist}" />
</target>
<target name="compile" depends="init">
<javac debug="true" srcdir="${src}" destdir="${build.classes}" includeantruntime="true">
<classpath refid="project.class.path" />
</javac>
</target>
<target name="job" depends="compile">
<jar jarfile="${dist}/${name}.jar">
<manifest>
<attribute name="Classpath" value="*.jar;*.properties;"/>
</manifest>
<zipfileset dir="${build.classes}"/>
<zipfileset dir="${lib}" prefix="lib" includes="**/*.*" excludes="hadoop-*.jar">
</zipfileset>
</jar>
</target>
<target name="clean">
<delete dir="${build}" />
<delete dir="${dist}" />
</target>
</project>
切记 第三方的jar代码里面不能调用另外第三方jar
的类文件否则会发生找不到另外第三方jar的类的
错误(原因不明),需要将调用另外第三方jar的
类的类文件改为自己项目的类