2017.3.12 更加深入了解MapReduce机制,学习使用Combiner类来对map的输出进行本地的合并。这里有个坑,真是,不自己写代码,不了解Combiner的机制啊。总结一句:Combiner只适合对map输出的结果集进行合并,合并成一个K,V值,这样可以大量减少shuffle阶段的网络传输。(网络是Hadoop最大的瓶颈)。这样看来,Combiner适合做的事情是,求一个K的最大值,最小值,对同一个K集合的合并之类的事情。诸如:排序,比较之类的事情不是很合适。(我是个菜鸟,所以想不到怎么解决这个问题,哪位大神看到这个,求帮忙啊。)还是之前的那个案例:有手机用户的流量数据,如果不写自己Combiner类,那么map会产生一对像这样的结果集在网络上传输:{138333333333,list(v1,v2,v3,............)}。写了自己Combiner类之后:网络上传输的数据会是这样:{138333333333,list(v1)}这样就完成了数据量的减少。
概念:Combiner继承了Reducer,可以理解成本地的Reducer。这里有个问题--->>必须符合这样的书写规则:combiner的输入和reduce的输入完全一致,输出和map的输出完全一致。
仔细的想了想这句话发现:map阶段输出的 k,v是combiner的输入,而combiner的输出又要和map的输出一致,那么combiner的输入和输出就必须一样。如果实现了这句话,那么combiner的输出就是Reduce的输入。这就等价于(map的输出就是reduce的输入。)----->>这样理解不知道对不对。所以这就有一个问题。如果我想排序,map的输出k,v集合的k必须是自己写的序列化类。而这样又不能实现在combiner中对同一手机号的流量累加。呵呵,心好累。好了,下面贴上代码:
-------------------------->>>自己写的序列化类:
package lcPhoneFolw;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import org.apache.hadoop.io.WritableComparable;
public class PhoneFlow implements WritableComparable{
private long upload;
private long download;
private long sumload;
public PhoneFlow(){}
@Override
public String toString() {
return upload+"\t"+download+"\t"+sumload;
}
public PhoneFlow(long upload, long download) {
this.upload = upload;
this.download = download;
this.sumload = upload+download;
}
public long getUpload() {
return upload;
}
public void setUpload(long upload) {
this.upload = upload;
}
public long getDownload() {
return download;
}
public void setDownload(long download) {
this.download = download;
}
public long getSumload() {
return sumload;
}
public void setSumload(long sumload) {
this.sumload = sumload;
}
//序列化
public void write(DataOutput out) throws IOException {
// TODO Auto-generated method stub
out.writeLong(upload);
out.writeLong(download);
out.writeLong(sumload);
}
//反序列化(应该是和序列化的顺序一样,如果不一样应该是会出问题。没有研究过)
public void readFields(DataInput in) throws IOException {
// TODO Auto-generated method stub
upload = in.readLong();
download = in.readLong();
sumload = in.readLong();
}
//自定义比较器,会在map输出结果 PhoneFlow为K时,自动按照这个比较器传给reduce
public int compareTo(PhoneFlow o) {
return this.sumload>o.getSumload() ? -1 : 1 ;
}
}
------------------------------------------->>>>combiner类
package lcPhoneFolw;
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
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;
public class PhoneAllDemo {
/**
* Mapper类
* @author admin
*
*/
static class PhoneMapper2 extends Mapper{
//一般在Map阶段传入的K是没什么作用的,V一般是一个数据文件,这个案例是统计手机总流量,并排序,所以输出的k,v先按照<13833333333,自己写的序列化类>输出
@Override
protected void map(LongWritable key, Text value,Context context)
throws IOException, InterruptedException {
//这里是对V来进行逻辑运算一个手机流量文件我们需要它的手机号,上行流量和下行流量所以
String line = value.toString();
//得到每行的字符串之后 ,分析数据文件找到想要的数据,如手机号,上行流量和下行流量
String[] strs = line.split("\t");
long phoneNumber =Long.valueOf(strs[0]);//将手机号转为long类型
//需要一个PhoneFlow的实例
long upload = Long.valueOf(strs[1]);
long download = Long.valueOf(strs[2]);
PhoneFlow pf = new PhoneFlow(upload,download);
context.write(new LongWritable(phoneNumber), pf);//输入k,v 这个k,v值会交给自己写的combiner类
}
}
/**
* 自己的定义的combiner类,这个类实现map后的数据在本地的诸如合并,计算等等。。。。为了减少网络传输
* combiner也是继承的Reducer,重写reduce方法,针对这个案例,我们是为了完成相同手机号在本地上的合并和流量累加
* @author admin
*
*/
static class PhoneCombiner extends Reducer{
@Override
protected void reduce(
LongWritable key,
Iterable values,Context context)
throws IOException, InterruptedException {
//业务需要我们将相同手机号的流量进行汇总,注意,当这个reduce方法接受到map传过来的k,v值时,是一个一个相同k的集合,一个集合运行一次reduce方法
//对V进行计算,注意,上面的map方法传进来的K,V为{135333333333,PhoneFlow}这个样子的 ,那么 我们遍历PhoneFlow,得到总流量可不可以累加?
long upload = 0;
long download = 0;
for (PhoneFlow value : values) {
upload +=value.getUpload();
download += value.getDownload();
}
PhoneFlow pf = new PhoneFlow(upload, download);//定义一个新的PhoneFlow的实例来接受累加过后的upload和download
//遍历完成,可以输出了,注意,输出的顺序,输出的k,v类型必须和map的输出类型一致。
context.write(key, pf);
}
}
/**注意:combine的输入和reduce的输入完全一致,输出和map的输出完全一致
* 真正的reduce方法,这里接受combiner的输出k,v值,输出为<手机号,PhoneFlow>
* @author admin
*
*/
static class PhoneReducer2 extends Reducer{
@Override
protected void reduce(
LongWritable key,
Iterable values,Context context)
throws IOException, InterruptedException {
//因为我们在combiner中已经对map的结果进行了合并,所以得到的k,v集合肯定只有一个元素
context.write(key,values.iterator().next());
}
}
/**
* 主方法
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
//这个类,就不多解释了
Configuration conf = new Configuration();
// conf.set("fs.defualtFS", "hdfs://192.168.1.101:8020");设置使用的集群环境,我这里使用本地测试,所以注掉了
Job job = Job.getInstance(conf);
//设置MapReduce的主类
job.setJarByClass(PhoneAllDemo.class);
//设置自己定义的combiner类
job.setCombinerClass(PhoneCombiner.class);
//下面就开始设定mapper类和reducer类
job.setMapperClass(PhoneMapper2.class);
job.setReducerClass(PhoneReducer2.class);
job.setMapOutputKeyClass(LongWritable.class);
job.setMapOutputValueClass(PhoneFlow.class);
job.setOutputKeyClass(LongWritable.class);
job.setOutputValueClass(PhoneFlow.class);
// job.setOutputKeyClass(PhoneFlow.class);
// job.setOutputValueClass(LongWritable.class);
//设定输入文件路径和输出路径
FileInputFormat.setInputPaths(job,new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
job.waitForCompletion(true);
}
}
以上就是今天学习到的内容。谢谢观看------->>>>我去想静静去了