MapReduce开发测试的代码,可以提交到Hadoop集群上测试运行,也可以在本机测试运行。由于我们的开发机是windows系统,我在尝试将代码提交到Hadoop集群上测试运行的时候,出现了一个接一个的错误。最后我还是采用了本机测试运行的方法,本文介绍的配置方法最终能够实现本地运行测试代码。当然如此测试的代码打包之后,上传到Hadoop集群上去,是可以正确执行的。
运行环境简介:
操作系统: Windows 7 sp1
Java软件版本: jdk1.8.0_121
Eclipse版本: Mars.2 Release (4.5.2)
Hadoop集群版本: hadoop-2.5.2.tar.gz
Eclipse的Hadoop插件版本: hadoop-eclipse-plugin-2.5.2.jar
hadoop-common版本: hadoop-common-2.2.0-bin-master.zip
0. 修改IP地址映射关系
由于Apache Hadoop相关的组件,对Hostname的依赖性非常大,很少直接使用IP地址。所以需要修改系统的host文件,以确定Hadoop集群中的IP地址与hostname的映射关系。
以我的Hadoop集群为例:
192.168.0.50 master.hadoop.frilab
192.168.0.51 slave01.hadoop.frilab
192.168.0.52 slave02.hadoop.frilab
192.168.0.53 slave03.hadoop.frilab
192.168.0.54 slave04.hadoop.frilab
1. 安装配置Hadoop 2.5.2的运行环境
将hadoop-2.5.2.tar.gz
(后文有下载链接)解压缩到某一目录下,这里我解压缩到B盘(这个盘符是不是很奇葩^_^)里。
下载hadoop-common-2.2.0-bin-master.zip
(后文有下载链接),将这个软件包中的hadoop.dll
和winutils.exe
两个文件复制到hadoop-2.5.2的bin文件夹中。
注意: 上边提到的两个文件,能够保证hadoop在Windows系统上能够运行起来。否则会出现java.io.IOException: Could not locate executable null\bin\winutils.exe in the Hadoop binaries.
的错误。
注意: 这里使用2.5.2版的hadoop,却是用2.2.0版本的hadoop-common,你也许会存在疑问。但是相信我,你没有看错,确实可以用。
接着修改系统的环境变量,添加两个变量,分别是:
HADOOP_HOME:B:\hadoop-2.5.2
,你需要将这个地址修改为你解压缩hadoop-2.5.2.tar.gz的地址。
HADOOP_USER_NAME:hadoop
,这个变量的值是你的Hadoop运行环境中的用户名。如果这个值不正确,在读写hdfs时会出现权限错误。
PATH:;%HADOOP_HOME%/bin
,在PATH变量上再增加一个目录,指向Hadoop的执行目录。
至此就完成了Hadoop 2.5.2运行环境的配置。
2. 在Eclipse中添加Hadoop插件
在Eclipse上安装Hadoop插件非常简单,只需要将hadoop-eclipse-plugin-2.5.2.jar
(后文有下载链接)文件放到Eclipse软件目录下的plugins文件夹下,重新启动Eclipse即可。
3. 在Eclipse中添加查看HDFS的功能
在Eclipse上添加Hadoop插件后,HDFS的管理窗口不会默认出现在主界面上,需要手动设置。
在主界面的菜单栏上以此选择Window -> Show View -> Other,界面弹出Show View界面,选择MapReduce Tools -> Map/Reduce Locations。
添加完成后可以在界面上看到如下的窗口,
点击右键,增加选择New Hadoop Location:
在配置界面上添加Location name,Map/Reduce、DFS的Host、Port,和User name。配置完成后点击确定即可。
注意:Map/Reduce Master的配置信息可以参考yarn-site.xml中的yarn.resourcemanager.scheduler.address
参数的配置,DFS Master的配置信息可以参考core-site.xml中的fs.defaultFS
参数的配置。
完成后可以在Map/Reduce Locations窗口上看到刚才添加的HDFS连接。
并且在Project Exploper窗口的DFS Locations中可以可以看到该连接,使用该连接可以打开目录,查看文件。
此时就可以使用eclipse查看,管理hdfs中的文件了。注意通过此方法可以上传、下载和删除文件,但是不可以编辑文件。
4. 编写实现WordCount功能的示例代码
示例代码使用Maven进行管理,在Eclipse上新建一个名为wordcount的Maven项目。
修改项目配置文件pom.xml,主要修改两个要点:修改软件库的依赖关系、修改编译打包参数。
修改软件库的依赖关系:
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>2.5.2</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-hdfs</artifactId>
<version>2.5.2</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>2.5.2</version>
</dependency>
修改编译打包参数:
<build>
<plugins>
<plugin>
<artifactId> maven-assembly-plugin </artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<!-- 指定入口类,这里按照实际情况修改成你创建的wordcount的类 -->
<mainClass>com.wordcount.WordCount</mainClass>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
该示例程序虽然是运行在本地,但是操作目录依然是在Hadoop集群的hdfs上边。所以需要hadoop集群的两个配置文件。hdfs-size.xml,core-site.xml。将这两个文件放在src\main\java目录下即可。
为了增加日志显示效果,需要在src\main\java目录下创建log4j.properties,文件,文件内容如下:
### set log levels ###
log4j.rootLogger = ALL, systemOut
log4j.appender.systemOut= org.apache.log4j.ConsoleAppender
log4j.appender.systemOut.layout= org.apache.log4j.PatternLayout
log4j.appender.systemOut.layout.ConversionPattern= [%-d{yyyy-MM-dd HH:mm:ss.SS}] [%p] : %m%n
log4j.appender.systemOut.Threshold= INFO
log4j.appender.systemOut.ImmediateFlush= TRUE
log4j.appender.systemOut.Target= System.out
log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
log4j.appender.D.File = logs/log.log
log4j.appender.D.Append = true
log4j.appender.D.Threshold = DEBUG
log4j.appender.D.layout = org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern = [%-d{yyyy-MM-dd HH:mm:ss.SS}] [%p] : %m%n
创建一个wordcount.java文件,文件内容如下:
package com.wordcount;
import java.io.IOException;
import java.util.StringTokenizer;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.util.GenericOptionsParser;
public class WordCount {
public static class TokenizerMapper extends Mapper<Object, Text, Text, IntWritable> {
private final static IntWritable one = new IntWritable(1);
private Text word = new Text();
public void map(Object key, Text value, Context context) throws IOException, InterruptedException {
System.out.println(value.toString());
StringTokenizer itr = new StringTokenizer(value.toString());
while (itr.hasMoreTokens()) {
word.set(itr.nextToken());
context.write(word, one);
}
}
}
public static class IntSumReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
private IntWritable result = new IntWritable();
public void reduce(Text key, Iterable<IntWritable> values, Context context)
throws IOException, InterruptedException {
int sum = 0;
for (IntWritable val : values) {
sum += val.get();
}
result.set(sum);
context.write(key, result);
}
}
public static void main(String[] args) throws Exception {
/**
* Configuration:map/reduce的j配置类,向hadoop框架描述map-reduce执行的工作
*/
Configuration conf = new Configuration();
@SuppressWarnings("deprecation")
Job job = new Job(conf, "word count"); // 设置一个用户定义的job名称
job.setJarByClass(WordCount.class);
job.setMapperClass(TokenizerMapper.class); // 为job设置Mapper类
job.setCombinerClass(IntSumReducer.class); // 为job设置Combiner类
job.setReducerClass(IntSumReducer.class); // 为job设置Reducer类
job.setOutputKeyClass(Text.class); // 为job的输出数据设置Key类
job.setOutputValueClass(IntWritable.class); // 为job输出设置value类
FileInputFormat.addInputPath(job, new Path("/input")); // 为job设置输入路径
FileOutputFormat.setOutputPath(job, new Path("/output"));// 为job设置输出路径
System.exit(job.waitForCompletion(true) ? 0 : 1); // 运行job
}
}
该程序的逻辑是,在hdfs的根目录下寻找input目录,遍历该目录下载所有文件,对这些文件内的内容按照空格分词,统计这些词出现的个数,并将统计结果存放在output目录下的文件中。
在HDFS中创建input目录,在该目录下上传两个文本文件file1.txt和file2.txt。
其中file1.txt的文件内容为filename is file1
,file2.txt的文件内容为the filename is file2
。
注意: 通过Eclipse中的Hadoop插件,可以直接在HDFS上创建文件夹,但是无法添加文件和编辑文件。只能在本地创建和编辑好文件后,再通过Eclipse上传到指定目录下。
查看HDFS中是否存在output目录,如果存在,首先删除该目录,否则会提示目录已存在的报错提示。运行该程序,点击右键,选择Run on Hadoop。
运行成功后,可以在HDFS中查看到一个output文件,改文件目录如下所示:
打开其中的part-r-00000文件,可以看到如下内容:
说明示例运行成功。
5. 测试程序打包,上传到集群上运行
执行此操作前,先删除hdfs上的output目录。否则再次运行程序的时候,会提示出现output已存在的错误。
在Eclipse上执行mvn install
,既可以生成可以运行的jar包。因为我们前面已经修改过项目的配置文件pom.xml了,修改的目的就是项目打包的时候,能够把代码运行所需要的所有库都打包到一个jar文件中。
该命令执行完毕后,会在wordcount项目中的target目录中出现一个wordcount-0.0.1-SNAPSHOT.jar
和wordcount-0.0.1-SNAPSHOT-jar-with-dependencies.jar
。后者就是包含依赖库的打包文件。
将wordcount-0.0.1-SNAPSHOT-jar-with-dependencies.jar
上传到Hadoop集群上去,运行如下命令来提交任务:
hadoop jar wordcount-0.0.1-SNAPSHOT-jar-with-dependencies.jar
运行成功后,就可以看到HDFS中出现output目录,并且能够得到与在eclipse中执行相同的结果。
能够打包到实际集群上并成功运行,表明我们在Windows上本地运行不会影响MapReduce程序开发的正确性。
附录
点击此处下载,下载的文件包括:hadoop 2.5.2.tar.gz,hadoop-common-2.2.0-bin-master.zip,hadoop-eclipse-plugin-2.5.2.jar。