考虑到Windows平台尽管界面友好,但Hadoop环境配置较"怪异",需借助cygwin,这个过程并不优雅。正好我手上另有一套ubuntu环境,用着也很顺手,就在ubuntu中安装了Eclipse IDE,在这套环境中配置安装Hadoop开发环境。
在本地搭建HADOOP开发环境时,三思也在网上参考了很多文章,还是很有感触,一个是果然天下文章一大抄(好吧我也没少抄,先抄官方文档,抄完改名叫笔记,本该汗颜的事儿被俺说的这么义正严词,是否当场就震断了你们坚强无比的氪合金神经,没错我最近在看厚黑学来着,可惜Eclipse配置HADOOP开发环境没有官方文档描述,否则我应该能(抄)写的更严谨一些),很多文章的内容看起来都显得近似,近似也就罢了,只要能有帮助就好,但遗憾不少文章过程描述的并不清晰,抄来抄去更显得糊里糊涂,咱不能说这些文章完全没有帮助,不过确实读的出来,很多人都是一知半解的写(或抄),我当然也好不到哪去,不过我比较耿直,我直接将这点明说了出来,省得大家瞎惦记,尽管水平很有限,不过我保证将尽最大努力,将整个配置过程清晰地展现出来。做为参考文档,不管操作者处在什么水平,按照我这个步骤都能将环境配置起来。
另一个是目前我在网上找到的文章,多都是在本地安装一套Local模式或Pseudo-distributed 模式的 HADOOP环境,并在此基础上配置Eclipse进行开发。我这里因为是先配置好一套HADOOP测试环境(配置过程参考:[三思笔记]Hadoop安装手册),不想重复性地在Eclipse本地再安装一套HADOOP,因此我希望我的Eclipse能够直接连接远程的HADOOP,并基于该套环境进行调试。
1、初始化环境
设定环境如下:
- HADOOP:0.20.2
- Namenode:hdnode1 - 192.168.30.203
- Datanode:hdnode{2-5} - 192.168.30.204-207
- Developer:ubuntu - 192.168.10.200 + eclipse 4.2 juno
从namenode或datanode节点中,复制HADOOP软件包到Developer环境,因为在代码开发过程中,需要HADOOP软件包的支持。
提示:
1、Eclipse可以直接到官网免费下载:www.eclipse.org/downloads
2、建议ubuntu下创建grid用户,并在grid用户中配置Eclipse开发环境,可以避免(或者说减少)连接HADOOP时用户权限等导致的异常。
3、Developer环境的HADOOP并不需额外进行配置,更完全没有必要将其加入到现有HADOOP环境中;
下载好Eclipse压缩包,我们要下载的是Eclipse IDE for Java EE Developers工具包,下载页面能够自动识别操作系统并给出建议的下载地址(Windows平台会给出windows版本的下载地址,linux平台则提供linux版本下载),一般用户只需要选择32位还是64位即可。
我这里下载的64位linux版本,以grid用户登录,将文件解压缩到适当目录下:
# su - grid
$ tar xvfc eclipse-jee-juno-SR1-linux-gtk-x86_64.tar.gz -C /data/developer/
在当前HADOOP环境中的任意一个节点上,打开hadoop主目录,找到contrib/eclipse-plugin/hadoop-0.20.2-eclipse-plugin.jar文件,将该文件复制至Eclipse程序目录的plugins目录内。
返回到Ubuntu视窗界面,双击eclipse程序主目录下的eclipse,进入Eclipse界面:
2、配置Eclipse开发环境
第一次进入Eclipse会提示你选择一个工作区,这个路径下将会做为工程文件的默认保存路径(保存或创建工程时也可以另外自定义,并且后期也可以进行修改),这里三思将其定义到/data/developer/workspace目录下。
点击菜单:Window -> Open Perspective -> Other,选中"Map/Reduce",打开MapReduce视图:
在右下窗口中能够找到"Map/Reduce Locations",正如名称所代表的,此处用来配置路径:
在小窗口的空白位置点击鼠标右键,选中"New Hadoop Location",弹出窗口如下:
在这里需要用户指定下列配置:
- Location name:为配置的路径指定一个名称,名称可以自定义,这里三思将之定义为JSSHadoop;
- Map/Reduce Master:根据hadoop环境中namenode节点conf/mapred-site.xml 文件中的配置而定,指定为mapred.job.tracker参数的值;
- DFS Master:根据hadoop环境中namenode节点conf/core-site.xml文件中的配置而定,指定为fs.default.name的值;
- User name:指定操作用户,一般默认此处显示为操作系统用户名;
配置好后点击Finish。而后在左侧的窗口中就会看到DFS Locations处多了我们刚刚配置的JSSHadoop:
通过层次点开,就能看到我们在HDFS中保存的目录和文件了(如果没有出现,就右键Refresh刷新):
通过这个区域,我们就可以读写HDFS文件系统中的目录和文件了。
不过若要调试HADOOP程序,还需要配置参数,指定Hadoop的软件安装路径,注意本步只是要指定Hadoop软件的路径,并不是要配置HADOOP环境,只需要下载(或复制)一份hadoop文件即可,在Hadoop Map/Reduce参数中指定适当的目录:
至此环境就配置好了,接下来可以创建Map/Reduce项目,进行开发测试。
3、创建项目
点击菜单:File -> New -> Project,在弹出的页面中选择Map/Reduce Project:
选中后弹出页面如下:
本页需要用户指定的内容如下:
- Project name:指定一个项目名称,完全自定义;
- Location:指定项目的存储路径,也可以自定义,默认则是保存在启动Eclipse时指定的工作区文件夹内;
- MapReduce库文件安装路径:就是HADOOP安装路径,使用默认即可;
项目创建后,Eclipse会把相关jar包自动导入。然后我们就可以在这个项目下编写程序,调试HADOOP了。
4、代码开发和调试示例
前面部署HADOOP时曾经测试过wordcount程序,这样我们在Eclipse也调试这一功能。HADOOP提供了这些示例的源代码,大家可以在HADOOP安装文件根路径下的examples目录下,比如WordCount位于:examples/org/apache/hadoop/examples/WordCount.java
我们新建一个java文件,右键选中项目名称,点击New -> Class创建一个新的Java Class文件,弹出窗口如下:
将示例代码直接复制进来,而后修改文件头部包名即可。新创建的WordCount.java文件内容如下:
package com.jss.hadoop.mapreduce.test;
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 {
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 conf = new Configuration();
String[] otherArgs = new GenericOptionsParser(conf, args).getRemainingArgs();
if (otherArgs.length != 2) {
System.err.println("Usage: wordcount <in> <out>");
System.exit(2);
}
Job job = new Job(conf, "word count");
job.setJarByClass(WordCount.class);
job.setMapperClass(TokenizerMapper.class);
job.setCombinerClass(IntSumReducer.class);
job.setReducerClass(IntSumReducer.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
FileInputFormat.addInputPath(job, new Path(otherArgs[0]));
FileOutputFormat.setOutputPath(job, new Path(otherArgs[1]));
System.exit(job.waitForCompletion(true) ? 0 : 1);
}
}
WordCount如要运行,需要指定两个参数,即代码中65行和66行所需指定的路径。针对这种情况,我们即可以改动代码,直接在此处写好目标路径(同时还需要将53-57行之间的代码注释)而后即可直接运行调试;也可以配置WordCount的调试运行环境,为其配置运行参数。这里我们选择后一种方式。
选择菜单:Run -> Run Configurations -> Java Application,点击窗口左上角处的图标:
新建一个配置,将弹出的窗口显示项切换到Arguments选项:
此处需要我们填写Program arguments,即指定程序运行所需参数,根据程序设定,此时需要指定两个参数,一个指定要处理的文件源路径,另一个是处理后文件的输出路径,中间以空格分隔。请根据实际情况指定参数,配置好后,即可点击Run运行。
如果配置正确,执行成功后,在HDFS中就会创建jssout文件夹,如上图所示,其中保存的文件,就是对源路径中数据处理后的输出结果。
若要操作HDFS中的目录和文件也是同理,继续创建文件(过程不演示)FileOper.java,代码如下:
$ more /data/developer/workspace/FirstHadoopProject/src/com/jss/hadoop/hdfs/test/FileOper.java
package com.jss.hadoop.hdfs.test;
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
public class FileOper {
public static void main(String[] args) throws Exception {
if (args.length < 1) {
System.out.println("Must define parameters!");
} else {
Configuration conf = new Configuration();
conf.set("fs.default.name", args[0]);
FileOper.listHDFSFiles(conf); // 显示目录结构
//FileOper.uploadLocal2HDFS(conf, args[1], args[2]); // 上传文件
//FileOper.createHDFSFile(conf, args[1], args[2]); // 创建文件
//FileOper.deleteHDFSFile(conf, args[1]); // 删除文件
//FileOper.readHDFSFile(conf, args[1]); // 读取文件
//FileOper.makeHDFSDirectory(conf, args[1]); // 创建目录
//FileOper.removeHDFSDirectory(conf, args[1]); // 删除目录
}
}
public static void listHDFSFiles(Configuration conf) throws IOException {
FileSystem fs = FileSystem.get(conf);
FileStatus files[] = fs.listStatus(new Path("/"));
for (FileStatus file : files) {
System.out.println(file.getPath());
}
}
public static void uploadLocal2HDFS(Configuration conf, String s, String d)
throws IOException {
FileSystem fs = FileSystem.get(conf);
Path src = new Path(s);
Path dst = new Path(d);
fs.copyFromLocalFile(src, dst);
fs.close();
System.out.println("Upload to " + conf.get("fs.default.name"));
}
public static void createHDFSFile(Configuration conf, String createFilePath,
String content) throws IOException {
FileSystem fs = FileSystem.get(conf);
FSDataOutputStream fsos = fs.create(new Path(createFilePath));
fsos.write(content.getBytes("UTF-8"));
fsos.close();
fs.close();
System.out.println("Succeeded created file " + createFilePath);
}
public static boolean deleteHDFSFile(Configuration conf, String dst)
throws IOException {
FileSystem fs = FileSystem.get(conf);
Path path = new Path(dst);
boolean isDeleted = fs.delete(path, true);
fs.close();
return isDeleted;
}
public static byte[] readHDFSFile(Configuration conf, String dst)
throws Exception {
FileSystem fs = FileSystem.get(conf);
Path path = new Path(dst);
if (fs.exists(path)) {
FSDataInputStream is = fs.open(path);
// get the file info to create the buffer
FileStatus stat = fs.getFileStatus(path);
// create the buffer
byte[] buffer = new byte[Integer.parseInt(String.valueOf(stat
.getLen()))];
is.readFully(0, buffer);
is.close();
fs.close();
return buffer;
} else {
throw new Exception("the file is not found .");
}
}
public static void makeHDFSDirectory(Configuration conf, String dst)
throws IOException {
FileSystem fs = FileSystem.get(conf);
fs.mkdirs(new Path(dst));
fs.close();
System.out.println("Succeeded created directory " + dst);
}
public static void removeHDFSDirectory(Configuration conf, String dst)
throws IOException {
FileSystem fs = FileSystem.get(conf);
fs.delete(new Path(dst), true);
fs.close();
System.out.println("Succeeded remove directory " + dst);
}
}
FileOper能够读取HDFS中的文件目录结构,操作文件和目录。程序在执行时,同样需要指定参数,具体步骤与前面操作WordCount的原理相同,就不一一演示了。
5、配置javadoc
IDE工具之所以易用,就是辅助功能做的好。在开发代码时,能够减化操作,或者提供帮助。比如javadoc功能,方便我们快速查询方法相关描述、示例等帮助信息,那么对于新引入的HADOOP相关包有没有javadoc支持呢?回答是肯定的,HADOOP软件包中就带有这些内容,位于HADOOP软件根路径的docs目录下。
若要启用hadoop相关包体中各方法的帮助,也可以通过配置实现。右键点击工程中hadoop-0.20.2-core.jar文件,选择Properties,弹出界面如下,我们选择Javadoc Location进行配置:
需要我们在此处指定Javadoc文件的路径,正如前面所说,HADOOP软件包中自带帮助文档,直接指定到该路径下,而后点击OK确认。随后在开发hdfs/mapreduce代码时,指定方法名就会自动在一旁显示出该方法的帮助信息。