1、定义一个Mapper类:
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:默认情况下,是mr框架所读到的一行文本的起始偏移量,LongWritable
* VALUEIN:默认情况下,是mr框架所读到的一行文本的内容,Text
*
* KEYOUT:是用户自定义逻辑处理完成之后输出数据中的key,word count中是单词,Text
* VALUEOUT:是用户自定义逻辑处理完成之后输出数据中的value,word count中是单词次数,IntWritable
*
* 因为所有数据都需要被序列化,而Java的Serializable序列化会包含很多类的冗余信息,所以不适用Java中的类型,而是使用hadoop封装的可序列化类型
*
* @author Administrator
*
*/
public class WordCountMapper extends Mapper{
/**
* map阶段的业务逻辑就写在自定义的map()方法中
* maptask会对每一行输入数据调用一次我们自定义的map()方法
*/
@Override
protected void map(LongWritable key, Text value, Mapper.Context context)
throws IOException, InterruptedException {
// 将maptask传给我们的文本内容先转换成String
String line = value.toString();
// 根据空格将这一行切分成单词
String[] words = line.split(" ");
// 将单词输出为
for (String word:words) {
// 将单词作为key,将次数1作为value,以便于后续的数据分发,可以根据单词分发,以便于相同单词汇总到相同的reduce task
context.write(new Text(word),new IntWritable(1));
}
}
}
2、定义一个Reducer类
import java.io.IOException;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
/**
* KEYIN:对应mapper输出的KEYOUT
* VALUEIN:对应mapper输出的VALUEOUT
*
* KEYOUT:是用户自定义逻辑处理完成之后输出数据中的key,word count中是单词,Text
* VALUEOUT:是用户自定义逻辑处理完成之后输出数据中的value,word count中是单词次数,IntWritable
*
* 因为所有数据都需要被序列化,而Java的Serializable序列化会包含很多类的冗余信息,所以不适用Java中的类型,而是使用hadoop封装的可序列化类型
*
* @author Administrator
*
*/
public class WordCountReducer extends Reducer{
/**
* 参数1:key是一组相同单词对的key
*/
@Override
protected void reduce(Text key, Iterable values,
Reducer.Context context) throws IOException, InterruptedException {
int count = 0;
for(IntWritable value : values){
count += value.get();
}
context.write(key, new IntWritable(count));
}
}
3、定义一个Job类
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;
/**
* 相当于一个yarn集群的客户端
* 需要在此封装我们的mr程序的相关运行参数,指定jar包
* 最后提交给yarn
* @author Administrator
*
*/
public class WordCountDriver {
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
Job job = Job.getInstance(conf);
//指定本程序的jar包所在的路径
job.setJarByClass(WordCountDriver.class);
//指定本业务job要使用的mapper业务类
job.setMapperClass(WordCountMapper.class);
//指定本业务job要使用的reducer业务类
job.setReducerClass(WordCountReducer.class);
//指定mapper输出数据的KV类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
//指定最终输出的数据的KV类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
//指定job的输入原始文件所在的目录
FileInputFormat.setInputPaths(job, new Path(args[0]));
//指定job的输出结果所在的目录
FileOutputFormat.setOutputPath(job, new Path(args[1]));
//将job中配置的相关参数,以及job所用的java类所在的jar包,提交给jarn去运行
/*job.submit(); */
//将job中配置的相关参数,以及job所用的java类所在的jar包,提交给jarn去运行,并且等待集群运行的返回结果,参数true表示是否打印集群反馈信息
boolean res = job.waitForCompletion(true);
System.exit(res?0:1);
1、定义一个Mapper类:
import java.io.IOException;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
public class FlowCountMapper extends Mapper{
@Override
protected void map(LongWritable key, Text value, Mapper.Context context)
throws IOException, InterruptedException {
// 将一行内容转成String
String line = value.toString();
String[] fields = line.split("\t");
// 取出手机号
String phoneNbr = fields[1];
//取出上行流量和下行流量
long upFlow = Long.parseLong(fields[fields.length - 3]);
long dnFlow = Long.parseLong(fields[fields.length - 2]);
context.write(new Text(phoneNbr), new FlowBean(upFlow, dnFlow));
}
}
2、定义一个Reducer类
import java.io.IOException;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
public class FlowCountReducer extends Reducer{
@Override
protected void reduce(Text key, Iterable values, Reducer.Context context)
throws IOException, InterruptedException {
long sum_upFlow = 0;
long sum_dnFlow = 0;
// 遍历所有bean,将其中的上行流量,下行流量分别累加
for (FlowBean flowBean : values) {
sum_upFlow += flowBean.getUpFlow();
sum_dnFlow += flowBean.getDnFlow();
}
context.write(key, new FlowBean(sum_upFlow, sum_dnFlow));
}
}
3、定义一个Job类:
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
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 com.newtouch.mr.provinceflow.ProvincePartitioner;
public class FlowCountDriver {
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
Job job = Job.getInstance(conf);
// 指定本程序的jar包所在的路径
job.setJarByClass(FlowCountDriver.class);
// 指定自定义的数据分区器
job.setPartitionerClass(ProvincePartitioner.class);
// 同时指定相应数据分区数量的reducetask:0,1,2,3,4
job.setNumReduceTasks(5);
// 指定本业务job要使用的mapper业务类
job.setMapperClass(FlowCountMapper.class);
// 指定本业务job要使用的reducer业务类
job.setReducerClass(FlowCountReducer.class);
// 指定mapper输出数据的KV类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(FlowBean.class);
// 指定最终输出的数据的KV类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(FlowBean.class);
// 指定job的输入原始文件所在的目录
FileInputFormat.setInputPaths(job, new Path(args[0]));
// 指定job的输出结果所在的目录
FileOutputFormat.setOutputPath(job, new Path(args[1]));
// 将job中配置的相关参数,以及job所用的java类所在的jar包,提交给jarn去运行
/* job.submit(); */
// 将job中配置的相关参数,以及job所用的java类所在的jar包,提交给jarn去运行,并且等待集群运行的返回结果,参数true表示是否打印集群反馈信息
boolean res = job.waitForCompletion(true);
System.exit(res ? 0 : 1);
}
}
4、定义一个Partitioner类:
如果要对统计数据进行分类处理(手机流量按照省份分类),那么需要自定义一个Partitioner,此时需要注意Job中设置的ReduceTask的数量必须大于分类数量
import java.util.HashMap;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Partitioner;
import com.newtouch.mr.flowsum.FlowBean;
/**
* 该类的作用是将手机号的流量统计后,按照省进行分区,并将结果归属于各省的文件夹中
*
* K2,V2:对应的是map输出的KV的类型
* @author Administrator
*
*/
public class ProvincePartitioner extends Partitioner{
public static HashMap proviceDict = new HashMap();
static{
proviceDict.put("136", 0);
proviceDict.put("137", 1);
proviceDict.put("138", 2);
proviceDict.put("139", 3);
}
@Override
public int getPartition(Text key, FlowBean value, int numPartitions) {
String prefix = key.toString().substring(0, 3);
Integer provinceId = proviceDict.get(prefix);
return provinceId == null?4:provinceId;
}
}
1、本地运行模式
(1)mapreduce程序是被提交给LocalJobRunner在本地以单进程的形式运行
(2)而处理的数据及输出结果可以在本地文件系统,也可以在hdfs上
(3)怎样实现本地运行?写一个程序,不要带集群的配置文件(本质是你的mr程序的conf中是否有
mapreduce.framework.name=local以及yarn.resourcemanager.hostname参数)
(4)本地模式非常便于进行业务逻辑的debug,只要在eclipse中打断点即可,如果在windows下想运行本地模式来测试程序逻辑,需要在windows中配置环境变量:
%HADOOP_HOME% = d:/hadoop-2.8.0
%PATH% = %HADOOP_HOME%\bin
并且要将d:/hadoop-2.8.0的lib和bin目录替换成windows平台编译的版本
2、集群运行模式
(1)将mapreduce程序提交给yarn集群resourcemanager,分发到很多的节点上并发执行
(2)处理的数据和输出结果应该位于hdfs文件系统
(3)提交集群的实现步骤:
A、将程序打成JAR包,然后在集群的任意一个节点上用hadoop命令启动
hadoop jar workcount.jar cn.gigdata.hdfs.mr.WordcountDriver /wordcount/input1 /wordcount/outputWord
B、直接在linux的eclipse中运行main方法(项目中要带参数:mapreduce.framework.name=yarn以及yarn的两个基本配置)
C、如果要在windows的eclipse中提交job给集群,则要修改YarnRunner类
(1)用户编写的程序分成三个部分:Mapper,Reducer,Driver(提交运行mr程序的客户端)
(2)Mapper的输入数据是KV对的形式(KV的类型可自定义)
(3)Mapper的输出数据是KV对的形式(KV的类型可自定义)
(4)Mapper中的业务逻辑写在map()方法中
(5)map()方法(maptask进程)对每一个
(6)Reducer的输入数据类型对应Mapper的输出数据类型,也是KV
(7)Reducer的业务逻辑写在reduce()方法中
(8)Reducetask进程对每一组相同k的
(9)用户自定义的Mapper和Reducer都要继承各自的父类
(10)整个程序需要一个Drvier来进行提交,提交的是一个描述了各种必要信息的job对象