前言
本实验在Hadoop基础上完成,请确保可以正确启动Hadoop服务。
基本任务1:环境配置
修改配置文件(文件路径为 /opt/jxxy/hadoop-2.6.5/etc/hadoop )。找到mapred-site.xml文件和yarn-site.xml文件,添加如图所示代码:
<property>
<name>mapreduce.framework.name</name>
<value>yarn</value>
</property>
<property>
<name>yarn.nodemanager.aux-services</name>
<value>mapreduce_shuffle</value>
</property>
图1 修改mapred-site.xml文件
图2 修改yarn-site.xml文件
基本任务2:编程WordCount
(1)首先创建一个新文件,命令行输入:for i in `seq 100000`;do echo "hello jxxy$i" >> test1.txt;done。
for i in `seq 100000`;do echo "hello jxxy$i" >> test1.txt;done
图3 创建一个新文件test1.txt
新建一个eclipse项目,导入修改配置后的mapred-site.xml,yarn-site.xml文件,导入hadoop_jars包,同时也导入配置Hadoop时修改的hdfs-site.xml,core-site.xml文件。
这里首先需要使用Xftp将需要导入的文件传输到主机。
图4 传输文件
右键src目录,点击import,选择Genneral目录下的File System,之后选择之前传输文件的目录,选择所需文件即可。
图5 import操作
图6 导入文件
(2)编程WordCount主类,MyMapper类,MyReducer类,制作jar包
首先创建WordCount主类。代码如下:
package com.jxxy.mr.test;
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.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
public class WordCount {
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
Job job = Job.getInstance(conf, "myjob");
job.setJar("/root/wordcount.jar");
// 设置输入/输出路径
Path inPath = new Path("/user/root/test1.txt");
Path outPath = new Path("/output/wordcount");
FileInputFormat.addInputPath(job, inPath);
if (outPath.getFileSystem(conf).exists(outPath)) {
outPath.getFileSystem(conf).delete(outPath, true); // 删除已存在的输出目录
}
FileOutputFormat.setOutputPath(job, outPath);
// 设置 Mapper 和 Reducer 类
job.setMapperClass(MyMapper.class);
job.setReducerClass(MyReducer.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
System.exit(job.waitForCompletion(true) ? 0 : 1);
}
}
创建MyMapper类,代码如下:
package com.jxxy.mr.test;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
import java.util.StringTokenizer;
public class MyMapper 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 tokens = new StringTokenizer(value.toString());
while (tokens.hasMoreTokens()) {
word.set(tokens.nextToken());
context.write(word, one); // 输出 <单词, 1>
}
}
}
创建MyReducer类,代码如下:
package com.jxxy.mr.test;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
public class MyReducer 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); // 输出 <单词, 总数>
}
}
可以先尝试运行程序方便后面制作jar包。这里注意启动Hadoop,通过jps命令查看进程。
图7 启动Hadoop
部署,制作jar包。右键项目,选择Export-Java-Runnable JAR file,导出为可运行的jar包,Launch Configuration选择运行的项目,Export destination选择一个目录,Library handling选择第二个更好。然后点击完成。
这里导出项目之前注意自己在虚拟机配置的Java版本,如果是Java7,则右键项目,点击properties,点击Java compiler,将Compiler compliance level选择1.7,如果是Java8,则选择默认的1.8,无需更改。否则运行程序时将报错。
图8 Java编译器选择
图9 制作jar包
先上传输入文件(也就是前面一开始创建的文件)到hdfs,这里可以选择一开始创建文件的时候顺便上传到目录。然后将jar包上传到集群(放在/root),可以选择使用Xftp将导出的jar包传输到虚拟机root/目录下。
图10 上传文件
图11 上传集群/root
(3)运行程序,统计test.txt文件hello和jxxy出现的次数。然后在浏览器查看,有_SUCCESS文件生成则说明编程成功(浏览器打开:主机名:50070)。
#运行
hadoop jar wordcount.jar com.jxxy.mr.test.WordCount
#查看输出目录
hadoop fs -ls /output/wordcount
#查看文件内容
hadoop fs -cat /output/wordcount/part-r-00000
#也可以选择输出少量输出
hadoop fs -cat /output/wordcount/part-r-00000 | head -n 10
图12 运行结果
图13 浏览器查看信息
进阶任务1:编程实现文件合并和去重操作
任务要求:对于两个输入文件,即文件A和文件B,编写程序对两个文件进行合并,并剔除其中重复的内容,得到一个新的输出文件C。
(1)首先创建测试文件A.txt和B.txt,然后上传到HDFS。
echo -e "apple\nbanana\norange\napple" > A.txt
echo -e "orange\ngrape\napple" > B.txt
hadoop fs -put A.txt B.txt /user/root/
图13 创建测试文件
图14 浏览器查看信息
(2)创建一个新的项目,编程MergeDriver主类,MergeMapper类,MergeReducer类,制作jar包。
创建MergeDriver主类,代码如下:
package MergeDriver;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.*;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import MergeDriver.MergeMapper;
import MergeDriver.MergeReducer;
public class MergeDriver {
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
Job job = Job.getInstance(conf, "MergeAndDeduplicate");
job.setJar("/root/merge.jar");
job.setJarByClass(MergeDriver.class);
job.setMapperClass(MergeMapper.class);
job.setReducerClass(MergeReducer.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(Text.class);
job.setNumReduceTasks(1); // 确保只有一个输出文件
// 输入文件
FileInputFormat.addInputPath(job, new Path("/user/root/A.txt"));
FileInputFormat.addInputPath(job, new Path("/user/root/B.txt"));
// 输出目录
Path outputDir = new Path("/output");
if (outputDir.getFileSystem(conf).exists(outputDir)) {
outputDir.getFileSystem(conf).delete(outputDir, true);
}
FileOutputFormat.setOutputPath(job, outputDir);
// 提交作业
boolean success = job.waitForCompletion(true);
// 作业成功后重命名结果文件
if (success) {
FileSystem fs = FileSystem.get(conf);
Path partFile = new Path(outputDir, "part-r-00000");
Path finalOutput = new Path(outputDir, "C.txt"); // 目标文件路径
if (fs.exists(partFile)) {
fs.rename(partFile, finalOutput); // 重命名文件
fs.delete(new Path(outputDir, "_SUCCESS"), true); // 删除_SUCCESS标记文件
System.out.println("结果已保存到 /output/C.txt");
}
}
System.exit(success ? 0 : 1);
}
}
创建MergeMapper类,代码如下:
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
public class MergeMapper extends Mapper<Object, Text, Text, Text> {
private Text line = new Text();
public void map(Object key, Text value, Context context)
throws IOException, InterruptedException {
line = value;
context.write(line, new Text("")); // Value 为空
}
}
创建MergeReducer类,代码如下:
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
public class MergeReducer extends Reducer<Text, Text, Text, Text> {
public void reduce(Text key, Iterable<Text> values, Context context)
throws IOException, InterruptedException {
context.write(key, new Text("")); // 去重:相同 Key 只输出一次
}
}
部署,制作jar包,先上传输入文件到hdfs,然后上传到集群(放在/root)。这里记得修改Java编译版本。
图15 制作jar包
图16 上传集群/root
(3)运行程序。命令行输入hadoop jar merge.jar MergeDriver运行,命令行输入hadoop fs -ls /output和hadoop fs -cat /output/C.txt验证结果。
hadoop jar merge.jar MergeDriver
hadoop fs -ls /output和hadoop fs -cat /output/C.txt
图17 验证结果
图18 浏览器查看信息
这里需要注意的是我在主类中删除了_SUCCESS文件,所以这里并不显示,同时我将目录以及生成的内容文件整合成了C.txt文件,从而符合任务要求。大家也可以在生成文件之后修改。
进阶任务2:编程实现对输入文件的排序
任务要求:现在有多个输入文件,每个文件中的每行内容均为一个整数。要求读取所有文件中的整数,进行升序排序后,输出到一个新的文件中,输出的整数格式为每行两个整数,第一个整数位第二个整数的排序位次,第二个整数位原待排列的整数。(具体样例参见教材P153)
(1)首先创建测试文件num1.txt和num2.txt,然后上传到HDFS。
# 创建测试文件
echo -e "30\n50\n10" > num1.txt
echo -e "10\n20\n40" > num2.txt
# 上传到HDFS
hadoop fs -put num1.txt num2.txt /user/root/
图19 创建测试文件
图20 浏览器查看信息
(2)创建一个新的项目,编程SortDriver主类,SortMapper类,SortReducer类,制作jar包。
创建SortDrive主类,代码如下:
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
public class SortDriver {
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
Job job = Job.getInstance(conf, "Integer Sort");
job.setJar("/root/sort.jar");
job.setJarByClass(SortDriver.class);
job.setMapperClass(SortMapper.class);
job.setReducerClass(SortReducer.class);
// 设置输出类型
job.setOutputKeyClass(IntWritable.class);
job.setOutputValueClass(IntWritable.class);
// 输入路径
FileInputFormat.addInputPath(job, new Path("/user/root/num1.txt"));
FileInputFormat.addInputPath(job, new Path("/user/root/num2.txt"));
// 输出路径
Path outputPath = new Path("/output/sort");
if (outputPath.getFileSystem(conf).exists(outputPath)) {
outputPath.getFileSystem(conf).delete(outputPath, true);
}
FileOutputFormat.setOutputPath(job, outputPath);
System.exit(job.waitForCompletion(true) ? 0 : 1);
}
}
创建SortMapper类,代码如下:
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
public class SortMapper extends Mapper<LongWritable, Text, IntWritable, IntWritable> {
private IntWritable number = new IntWritable();
@Override
protected void map(LongWritable key, Text value, Context context)
throws IOException, InterruptedException {
try {
int num = Integer.parseInt(value.toString().trim());
number.set(num);
context.write(number, new IntWritable(1)); // 输出格式: <数字, 1>
} catch (NumberFormatException e) {
System.err.println("忽略非整数行: " + value);
}
}
}
创建SortReducer类,代码如下:
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
import java.util.Iterator;
public class SortReducer extends Reducer<IntWritable, IntWritable, Text, IntWritable> {
private Text rankText = new Text();
private int counter = 1;
@Override
protected void reduce(IntWritable key, Iterable<IntWritable> values,
Context context) throws IOException, InterruptedException {
Iterator<IntWritable> iterator = values.iterator();
while (iterator.hasNext()) {
iterator.next(); // 消耗值但不使用
rankText.set(Integer.toString(counter));
context.write(rankText, key);
counter++;
}
}
}
部署,制作jar包,先上传输入文件到hdfs,然后上传到集群(放在/root)。这里同样需要注意Java编译版本问题。
图21 制作jar包
图22 上传集群/root
(3)运行程序。命令行输入
hadoop jar sort.jar SortDriver
hadoop fs -cat /output/sort/part-r-00000
运行,命令行输入hadoop fs -ls /output和hadoop fs -cat /output/sort/part-r-00000验证结果。
图31 验证结果
图32 浏览器查看信息
至此所有任务基本完成,相信大家也已经掌握了mapreduce编程的技巧。