紧接上一篇,为了把MapReduce的示例搞明白,需要先把Hadoop上的java编译调试环境给整出来,毕竟,一些执行流程的具体细节被封装在了框架中,仅仅靠公开的源代码静态的解读还是太费事了。有了调试器就要方便得多,理解起来也会省事不少。
一、构建基于CODE的Java编程调试环境
1.安装配置Maven插件
在已经安装好Single Node工作模式的Hadoop节点上,按照之前介绍过的方法,首先构建VSCODE环境。要在CODE上构建Java编程环境,需要安装Extension Pack For Java 插件包,主要安装了核Maven有关的6个插件:
插件安装完成后记得重启一下VSCODE,否则在使用maven构建框架的时候有一定可能性会遇到莫名其妙退出的情况……
新建一个testmr文件夹作为工作空间(比如本例在/root/testmr处),在该文件下环境下启动CODE。然后在文件标签页下面,也就是testmr这个空的工作空间下,右键,选择从Maven原型创建新项目:
选择maven-archetype-quickstart构建一个Java框架
在弹出的版本选择中,捡版本高的整吧:
取个自己的组织名字(比如com.pigshome),注意这个名字后面会需要用到:
给生成的框架起个名字(比如testmapreduce),框架会在testmr工作目录下生成一个名为testmapreduce的目录,并存放生成的App文件及测试文件。
接下来会弹出一个对跨狂,要求选择项目所在的目录,我们将/root/testmr工作目录就作为项目的目录,所以选好了直接右上角的“selectDestination Folder”点击就行,然后等待CODE转啊转,此时是Maven在执行框架生成任务:
转到进入交互式模式的时候,Maven会询问一些配置选择,不停的回车啊回车就可以了。
到这里,框架就算生成完了。Maven会弹出一个对话框询问是新打开一个CODE还是使用当前这个。选open会新开一个,选add to worksapce就使用当前的。
点开工作空间中testmapreduce的目录,会发现其下src目录下,有main、test两个子目录。我们主要在main下折腾,所以可以点开其下的App.java文件,这是一个java版本的hello world。
选择调试标签,点击“运行和调试”,会启动当前打开的文件,也就是App.java。环境配置正常的话,应该会不出意外的输出“hello world!”。
2.认识Pom.xml
话说Maven作为java的编译环境构建者,其主要的环境配置信息是存储在名为Pom.xml的文件中的,这个在我们打开testmapreduce文件夹的时候就会发现,其内容如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.pigshome</groupId>
<artifactId>testmapreduce</artifactId>
<version>1.0-SNAPSHOT</version>
<name>testmapreduce</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<!-- 这里可以设置属性,类似宏变量,后面可以使用${名称}的方式引用
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
<!-- 这里配置所有依赖项-->
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
<!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
<plugin>
<artifactId>maven-site-plugin</artifactId>
<version>3.7.1</version>
</plugin>
<plugin>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>3.0.0</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
但在pom.xml文件中,目前除了properties组和dependencies组需要我们更改——实际一般dependencies就够了,其它可以暂时不关心。
——本文关于环境搭建的主要内容我们参考了VSCode+Maven+Hadoop开发环境搭建一文。该文中使用属性定义了版本号,故而在发生版本替代时,可以比较简单的更新一下属性的值就行。不过,在我们的试验中,更换版本号的事还是较少发生的,所以就不折腾了。:)
二、配置MapReduce调试环境
1.配置Hadoop依赖项仓库
在Maven的仓库里搜索Hadoop的配置方法,按照Hadoop的要求增加依赖项。建议不要随意添加,那样可能会造成一些莫名奇妙的错误,比如dfs无法启动之类。
再度核实一下版本号,别弄错了
以hadoop-client为例
点击3.3.3(因为我的hadoop安装版本是3.3.3)
从Maven中复制
<!-- https://mvnrepository.com/artifact/org.apache.hadoop/hadoop-client -->
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>3.3.3</version>
<scope>provided</scope>
</dependency>
将其放入dependencies组中。此时pom.xml如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.pigshome</groupId>
<artifactId>testmapreduce</artifactId>
<version>1.0-SNAPSHOT</version>
<name>testmapreduce</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.hadoop/hadoop-client -->
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>3.3.3</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.hadoop/hadoop-common -->
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>3.3.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.hadoop/hadoop-hdfs -->
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-hdfs</artifactId>
<version>3.3.3</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.hadoop/hadoop-yarn-api -->
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-yarn-api</artifactId>
<version>3.3.3</version>
</dependency>
</dependencies>
<build>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
<!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
<plugin>
<artifactId>maven-site-plugin</artifactId>
<version>3.7.1</version>
</plugin>
<plugin>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>3.0.0</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
看看是否设置成功,在App.java中加入hadoop的支持:
package com.pigshome;
import java.util.Random;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.*;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.input.SequenceFileInputFormat;
import org.apache.hadoop.mapreduce.lib.map.InverseMapper;
import org.apache.hadoop.mapreduce.lib.map.RegexMapper;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.mapreduce.lib.output.SequenceFileOutputFormat;
import org.apache.hadoop.mapreduce.lib.reduce.LongSumReducer;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;
/**
* Hello world!
*
*/
public class App
{
public static void main( String[] args )
{
System.out.println( "Hello World!" );
}
}
仍然能够成功运行,证明导入是成功的,虽然黄线部分会警告说导入的包都没用过
2. 导入MapReduce项目
如上篇最后所述,Hadoop示例教程中使用了Grep,一个对输入文件中每行字符进行正则匹配并统计命中的MapReduce算法。Grep的源文件在hadoop/share/hadoop/mapreuce/sources/目录下,名字为hadoop-mapreduce-examples-3.3.3-sources.jar
(1)拷贝源文件
使用jar命令将其解压出来,找到Grep.java文件。
[root@pig1 testmr]# mkdir examples
[root@pig1 testmr]# cd examples
[root@pig1 examples]# jar -xvf /root/hadoop/share/hadoop/mapreduce/sources/hadoop-mapreduce-examples-3.3.3-sources.jar
已创建: META-INF/
已解压: META-INF/MANIFEST.MF
已解压: META-INF/LICENSE.txt
已解压: META-INF/NOTICE.txt
已创建: org/
已创建: org/apache/
已创建: org/apache/hadoop/
已创建: org/apache/hadoop/examples/
已创建: org/apache/hadoop/examples/dancing/
已创建: org/apache/hadoop/examples/terasort/
已创建: org/apache/hadoop/examples/terasort/2009-write-up/
已创建: org/apache/hadoop/examples/pi/
已创建: org/apache/hadoop/examples/pi/math/
已解压: org/apache/hadoop/examples/WordCount.java
已解压: org/apache/hadoop/examples/WordMedian.java
已解压: org/apache/hadoop/examples/BaileyBorweinPlouffe.java
已解压: org/apache/hadoop/examples/MultiFileWordCount.java
已解压: org/apache/hadoop/examples/dancing/OneSidedPentomino.java
已解压: org/apache/hadoop/examples/dancing/DancingLinks.java
已解压: org/apache/hadoop/examples/dancing/DistributedPentomino.java
已解压: org/apache/hadoop/examples/dancing/Pentomino.java
已解压: org/apache/hadoop/examples/dancing/Sudoku.java
已解压: org/apache/hadoop/examples/dancing/package.html
已解压: org/apache/hadoop/examples/dancing/puzzle1.dta
已解压: org/apache/hadoop/examples/Join.java
已解压: org/apache/hadoop/examples/AggregateWordCount.java
已解压: org/apache/hadoop/examples/WordMean.java
已解压: org/apache/hadoop/examples/ExampleDriver.java
已解压: org/apache/hadoop/examples/terasort/TeraSortConfigKeys.java
已解压: org/apache/hadoop/examples/terasort/TeraScheduler.java
已解压: org/apache/hadoop/examples/terasort/GenSort.java
已解压: org/apache/hadoop/examples/terasort/TeraGen.java
已解压: org/apache/hadoop/examples/terasort/Random16.java
已解压: org/apache/hadoop/examples/terasort/TeraValidate.java
已解压: org/apache/hadoop/examples/terasort/TeraOutputFormat.java
已解压: org/apache/hadoop/examples/terasort/TeraSort.java
已解压: org/apache/hadoop/examples/terasort/TeraInputFormat.java
已解压: org/apache/hadoop/examples/terasort/2009-write-up/1PBTaskTime.png
已解压: org/apache/hadoop/examples/terasort/2009-write-up/100TBTaskTime.png
已解压: org/apache/hadoop/examples/terasort/2009-write-up/500GBTaskTime.png
已解压: org/apache/hadoop/examples/terasort/2009-write-up/Yahoo2009.tex
已解压: org/apache/hadoop/examples/terasort/2009-write-up/tera.bib
已解压: org/apache/hadoop/examples/terasort/2009-write-up/1TBTaskTime.png
已解压: org/apache/hadoop/examples/terasort/Unsigned16.java
已解压: org/apache/hadoop/examples/terasort/TeraChecksum.java
已解压: org/apache/hadoop/examples/terasort/package.html
已解压: org/apache/hadoop/examples/SecondarySort.java
已解压: org/apache/hadoop/examples/DBCountPageView.java
已解压: org/apache/hadoop/examples/pi/Parser.java
已解压: org/apache/hadoop/examples/pi/TaskResult.java
已解压: org/apache/hadoop/examples/pi/DistSum.java
已解压: org/apache/hadoop/examples/pi/DistBbp.java
已解压: org/apache/hadoop/examples/pi/SummationWritable.java
已解压: org/apache/hadoop/examples/pi/Container.java
已解压: org/apache/hadoop/examples/pi/Combinable.java
已解压: org/apache/hadoop/examples/pi/math/Modular.java
已解压: org/apache/hadoop/examples/pi/math/Summation.java
已解压: org/apache/hadoop/examples/pi/math/LongLong.java
已解压: org/apache/hadoop/examples/pi/math/Montgomery.java
已解压: org/apache/hadoop/examples/pi/math/Bellard.java
已解压: org/apache/hadoop/examples/pi/math/package.html
已解压: org/apache/hadoop/examples/pi/math/ArithmeticProgression.java
已解压: org/apache/hadoop/examples/pi/Util.java
已解压: org/apache/hadoop/examples/pi/package.html
已解压: org/apache/hadoop/examples/Grep.java
已解压: org/apache/hadoop/examples/AggregateWordHistogram.java
已解压: org/apache/hadoop/examples/Sort.java
已解压: org/apache/hadoop/examples/WordStandardDeviation.java
已解压: org/apache/hadoop/examples/QuasiMonteCarlo.java
已解压: org/apache/hadoop/examples/RandomWriter.java
已解压: org/apache/hadoop/examples/RandomTextWriter.java
已解压: org/apache/hadoop/examples/package.html
[root@pig1 examples]# ls
META-INF org
将其拷贝到CODE项目中的源码目录下。根据JAVA的默认项目框架,源代码应该在工作目录testmapreduce下的src/main/java/com/pigshome下,当然这里com/pigshome是我的项目标识。
[root@pig1 examples]# cp org/apache/hadoop/examples/Grep.java ../testmapreduce/src/main/java/com/pigshome
[root@pig1 examples]# cd ../testmapreduce/src/main/java/com/pigshome
[root@pig1 pigshome]# ls
App.java Grep.java
[root@pig1 pigshome]#
所以,需要在Grep中,将下图中红线指出包名改成我们自己的包名,如com.pigshome,和App.java中的一样。
(2)配置输入参数
如同示例一样,需要准备一个输入目录,并将hadoop/etc/hadoop/下的几个xml文件当小白鼠。至于输入目录,其实随便找个地方就行,我们是就在测试程序的同目录下建了一个input目录
[root@pig1 testmr]# mkdir input
[root@pig1 testmr]# cp /root/hadoop/etc/hadoop/*.xml input
[root@pig1 testmr]# ls
examples input testmapreduce
[root@pig1 testmr]# ls input
capacity-scheduler.xml hdfs-rbf-site.xml kms-acls.xml yarn-site.xml
core-site.xml hdfs-site.xml kms-site.xml
hadoop-policy.xml httpfs-site.xml mapred-site.xml
[root@pig1 testmr]#
准备好以后,需要配置项目的launch.json文件,用来指定调试启动时的命令行参数。
在CODE的调试标签页中选择自定义lauch.json文件, 生成launch.json文件如下所示:
因为我们的main/java/com/pigshome目录下有App.java和Grep.java两个文件,所以生成的launch.json文件中包含了当前文件、App、Grep共3个启动选择项。所以,根据这个文件夹底下的文件不同,可能launch.json文件的项数会有不同。我们需要更改的是Grep这个启动项的参数。
参考上一篇中我们测试时候的参数,指定我们准备的输入文件夹的位置,在Lauch Grep的组中增加"args"选项,填入输入参数。
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "java",
"name": "Launch Current File",
"request": "launch",
"mainClass": "${file}"
},
{
"type": "java",
"name": "Launch App",
"request": "launch",
"mainClass": "com.pigshome.App",
"projectName": "testmapreduce"
},
{
"type": "java",
"name": "Launch Grep",
"request": "launch",
"mainClass": "com.pigshome.Grep",
"projectName": "testmapreduce",
"args": "/root/testmr/input /root/testmr/output 'dfs[a-z.]+'"
}
]
}
因为有3个启动项,所以在调试菜单中需要选择一下:
选择Launch Grep,然后启动,执行完毕后,可以直接在终端中输入cat output/* 参看结果。
当然,这是因为我们的工作目录就下testmr下,而参数指明了output生成在testmr下。这里需要提一下,前面我们并没有手工生成output,因为hadoop并不希望我们代劳。如果这个目录已经存在,Grep程序会报错……
3.准备源代码支持
一般来说,在Maven的支持下,作为开源项目的Hadoop,其内部实现的源码应该是可以在我们搭建的调试环境中,以右键“转到定义”或者“转到引用”等直接查看的。但不幸的是,在我搭建的3.3.4环境下,尚能看到RegexMapper.java源文件,在3.3.3环境下,就看不到了,Maven会给出无法找到源文件的错误,所以其自己根据调用关系逆向分析,生成一个空的RegexMapper类。
更崩溃的是,我搭的两个一摸一样的3.3.4的环境,虽然都能看见RegexMapper类的源码,但一个有Configuration的源码,另一个死活都没有。
一开始我以为是我的maven设置除了问题,一通找,包括在settings.json文件中增加maven.downloadsources选项也没有用。还好后来意识到一件事,顺着这个思路不仅搞定了这个问题,捎带脚着也解决了离线环境下的调试环境构建问题:
首先捋清楚的是,maven是一个jar依赖包构建工具(这句话很普通,含义很关键)。一个java程序会用到很多jar包,并且这些jar包之间还会有复杂的依赖关系,手工添加会相当麻烦,所以要maven来帮忙。——这个听起来是不是十分的耳熟,这个怎么这么像yum呢……
其次,我们在pom.xml中填写的那些东西,实际和我们在yum.repo.d目录下构建repo文件时写的没什么两样。那些内容,只不过是知道maven到mvnrepository网站上去下载对应的jar包罢了。这个也和yum异曲同工。
所以,源码问题大概率是库本身就有问题(出于更重莫名原因的没有下载下来),没有下载到我们需要的依赖项和源文件。碰到这种库问题的时候,我们有这么2种方法去解决: 一是更换源,不过有可能新的源也不见得有;二是构建本地源,就像yum的createrepo一样。但是我也没啥兴趣去研究maven的命令,不知道如何下载并构建本地源,但是有一点是比较容易的——就是等maven下载好了,我们拷贝并且改造一下:
最后,我们只需要知道,maven下载的库是存放在~/.m2文件夹下的,这是个隐藏文件夹,所以ls需要-a一下,使用窗口要记得打开“显示隐藏文件”。和hadoop相关的在repository/org/apache/hadoop目录下
对于Configuration的源文件,在hadoop-common/3.3.4下面,如果这个文件夹下只有hadoop-common-3.3.4.jar而没有hadoop-common-3.3.4-sources.jar,那么是不可能看见源代码的。解决方法也很简单,找到hadoop-common-3.3.4-sources.jar并且拷贝过来就行。这个文件,要么在能够看见源码的环境的对应地方,要么可以直接从hadoop网站上去下载的hadoop-3.3.4-src.tar.gz里面找到。
到这里,再打一个节吧,贴的图太多了,每次往上翻都很难受,再开一贴。