编写mapreduce程序,习惯用eclipse,可以利用hadoop插件,引用第三方jar文件,然后直接run on hadoop即可,很方便。当然插件和eclipse的版本要匹配,不然总是local执行。但如果将自己的程序发布成jar文件,然后用hadoop命令行执行,则会遇到依赖类找不到问题:NoClassDefFoundError。
要解决这个问题,就需要了解hadoop命令式如何执行的?$HADOOP_HOME/bin/hadoop是一个脚本文件。Hadoop作业提交分析中分析了这个脚本,脚本最终执行的是
- <span style="font-size:16px;">exec "$JAVA" $JAVA_HEAP_MAX $HADOOP_OPTS -classpath "$CLASSPATH" $CLASS "$@"</span>
- hadoop jar example.jar wordcount input output
现在来分析下job的提交过程,先看图
- run job: 通过Job类实际上调用了JobClient的submitJobInternal提交作业
- get new job ID: JobClient通过JobSubmissionProtocol的实例来获得一个作业号,这个实例的初始化如下所示
- <span style="font-size:16px;">String tracker = conf.get("mapred.job.tracker", "local");
- if ("local".equals(tracker)) {
- this.jobSubmitClient = new LocalJobRunner(conf);
- } else {
- this.jobSubmitClient = createRPCProxy(JobTracker.getAddress(conf), conf);
- }</span>
- copy job resource: client将作业所需资源上传到hdfs上,如job split、jar文件等。其中jar文件时我们主要分析的,JobClient通过configureCommandLineOptions函数处理jar文件,顾名思义,该方法是处理命令行的参数。方法中通过job获得这些参数内容
- <span style="font-size:16px;">files = job.get("tmpfiles");
- libjars = job.get("tmpjars");
- archives = job.get("tmparchives");</span>
- <span style="font-size:16px;">if (files != null) {
- FileSystem.mkdirs(fs, filesDir, mapredSysPerms);
- String[] fileArr = files.split(",");
- for (String tmpFile: fileArr) {
- Path tmp = new Path(tmpFile);
- Path newPath = copyRemoteFiles(fs,filesDir, tmp, job, replication);
- try {
- URI pathURI = new URI(newPath.toUri().toString() + "#" + newPath.getName());
- DistributedCache.addCacheFile(pathURI, job);
- } catch(URISyntaxException ue) {
- //should not throw a uri exception
- throw new IOException("Failed to create uri for " + tmpFile);
- }
- DistributedCache.createSymlink(job);
- }
- }
- 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);
- }
- }
- if (archives != null) {
- FileSystem.mkdirs(fs, archivesDir, mapredSysPerms);
- String[] archivesArr = archives.split(",");
- for (String tmpArchives: archivesArr) {
- Path tmp = new Path(tmpArchives);
- Path newPath = copyRemoteFiles(fs, archivesDir, tmp, job, replication);
- try {
- URI pathURI = new URI(newPath.toUri().toString() + "#" + newPath.getName());
- DistributedCache.addCacheArchive(pathURI, job);
- } catch(URISyntaxException ue) {
- //should not throw an uri excpetion
- throw new IOException("Failed to create uri for " + tmpArchives);
- }
- DistributedCache.createSymlink(job);
- }
- }</span>
- JobClient提交job,JobTracker以及TaskTracker工作就不展开了。
经过对job提交过程的分析可知,要想让mapreduce程序引用第三方jar文件,可以采用如下方式
- 通过命令行参数传递jar文件,如-libjars等;
- 直接在conf中设置,如conf.set(“tmpjars”,*.jar),jar文件用逗号隔开;
- 利用分布式缓存,如DistributedCache.addArchiveToClassPath(path, job),此处的path必须是hdfs,即自己讲jar上传到hdfs上,然后将路径加入到分布式缓存中,本质是一样的;
- 第三方jar文件和自己的程序打包到一个jar文件中,程序通过job.getJar()将获得整个文件并将其传至hdfs上。
不过从开发角度来看,还是eclipse插件比较方便,或者采用Hadoop作业提交分析的方法,程序员好懒!