作为一个hadoop的初学者,在经历了一系列繁琐复杂的hadoop集群环境安装配置之后,终于自主完成了一个wordcount程序。通过mapreduce进行分布式运算,并通过yarn进行运行调度。
wordcount是一个经典的案例,相信大家都熟悉。主要任务就是计算每个单词出现的次数并保存。实现该过程,主要包括两个阶段:map阶段: 将每一行文本数据变成<单词,1>这样的kv数据;reduce阶段:将相同单词的一组kv数据进行聚合,即累加所有的v。主要包括三个类的开发:WordcountMapper类开发;WordcountReducer类开发;JobSubmitter客户端类开发。
WordcountMapper类:
package ldp.wordcount;
import java.io.IOException;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
/**
* KEYIN:是map task读取到的数据的key类型,是一行的起始偏移量Long
* VALUEIN:是map task读取到的数据的value类型,是一行的内容String
* KEYOUT:是用户自定义map方法要返回的结果kv数据的key类型,在wordcount逻辑中,为单词String
* VALUEOUT:是用户自定义map方法要返回的结果kv数据的value类型,在wordcount逻辑中,为整数Integer
*
* 在mapreduce中,map产生的数据需要传输给reduce,需要进行序列化和反序列化,所以hadoop设计了自己
* 的序列化机制
* hadoop为jdk中的常用基本类型序列化接口:
* Long LongWritable
* String Text
* Integer IntWritable
* Float FloatWritable
*/
public class WordCountMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
@Override
protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, Text, IntWritable>.Context context)
throws IOException, InterruptedException {
//切单词,将每一行数据按照分割符" "切分
String line = value.toString();
String[] words = line.split(" ");
for (String word : words) {
context.write(new Text(word), new IntWritable(1));
}
}
}
WordcountReducer类:
package ldp.wordcount;
import java.io.IOException;
import java.util.Iterator;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
public class WordCountReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
@Override
protected void reduce(Text key, Iterable<IntWritable> values,
Reducer<Text, IntWritable, Text, IntWritable>.Context context) throws IOException, InterruptedException {
//统计词频,values为一个迭代对象
Iterator<IntWritable> iterator = values.iterator();
int count = 0;
while (iterator.hasNext()) {
IntWritable value = (IntWritable) iterator.next();
count += value.get();
}
context.write(key, new IntWritable(count));
}
}
JobSubmitter客户端类:
package ldp.mapreduce;
import java.net.URI;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
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;
/**用于提交mapreduce job的客户端程序
*功能:
*1、封装本次job运行时所需要的必要参数
*2、跟yarn进行交互,将mapreduce程序成功的启动运行
*/
public class JobSubmitter {
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
Job job = Job.getInstance(conf);
//封装参数:jar包所在的位置
job.setJarByClass(JobSubmitter.class);
//封装参数:本次job要调用的Mapper实现类、Reducer实现类
job.setMapperClass(WordCountMapper.class);
job.setReducerClass(WordCountReducer.class);
//封装参数:本次job的Mapper实现类、Reducer实现类产生的结果数据的key、value类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
//封装参数:本次job要处理的输入数据集所在路径、最终结果的输出路径
//注意:此时输入路径为hadoop集群环境的目录
FileInputFormat.setInputPaths(job, new Path("/wordcount/input"));
//注意:输出路径必须不存在,否则报错
FileOutputFormat.setOutputPath(job, new Path("/wordcount/output"));
//封装参数:想要启动的reducer task的数量
job.setNumReduceTasks(2);
//提交job给yarn
boolean res = job.waitForCompletion(true);
//用于以后的日志需要,可删掉
System.exit(res?0:-1);
}
}
三个类都完成以后,就需要将工程打成一个jar包并上传至linux服务器。然后在hadoop集群的机器上,用命令
hadoop jar wordcount.jar ldp.wordcount.JobSubmitter运行,hadoop jar命令会将这台机器上的hadoop安装目录中的所有jar包和配置文件全部加入运行时的classpath,此时运行结束,大功告成。
同时,有时候需要调试,也可以在本地运行,只需要把JobSubmitter客户端类的
FileInputFormat.setInputPaths(job, new Path("/wordcount/input"));
FileOutputFormat.setOutputPath(job, new Path("/wordcount/output"));
修改成本地目录,即:
FileInputFormat.setInputPaths(job, new Path("e:/wordcount/input"));
FileOutputFormat.setOutputPath(job, new Path("e:/wordcount/output"));
此时,mapreduce程序就会在本机运行,同时可以轻松调试及debug。