Hadoop(十六)之使用Combiner优化MapReduce
前言
前面的一篇给大家写了一些MapReduce的一些程序,像去重、词频统计、统计分数、共现次数等。这一篇给大家介绍的是关于Combiner优化操作。
一、Combiner概述
1.1、为什么需要Combiner
我们map任务处理的结果是存放在运行map任务的节点上。
map处理的数据的结果在进入reduce的时候,reduce会通过远程的方式去获取数据。
在map处理完数据之后,数据量特别大的话。reduce再去处理数据它就要通过网络去获取很多的数据。
这样会导致一个问题是:大量的数据会对网络带宽造成一定的影响。
有没有一种方式能够类似reduce一样,在map端处理完数据之后,然后在reduce端进行一次简单的数据处理?
MapReudce正常处理是:
map处理完,中间结果存放在map节点上。reduce处理的数据通过网络形式拿到reduce所在的节点上。
如果我们能够在map端进行一次类似于reduce的操作,这样会使进入reduce的数据就会少很多。
我们把在map端所执行的类似于reduce的操作成为Combiner。
1.2、Combiner介绍
1) 前提
每一个map都可能会产生大量的本地输出
2)Combiner功能
对map端的输出先做一次合并
3)目的
减少在map和reduce节点之间的数据传输量, 以提高网络IO性能。
二、使用Combiner优化Mapduce执行
2.1、使用前提
不能对最原始的map的数据流向reduce造成影响。也就是说map端进入reduce的数据不收Combiner的影响。
数据输入的键值类型和数据输出的键值类型一样的reduce我们可以把它当做Combiner来使用
举例:
我们前面一篇博客中有一个处理的是求用户的好友列表的数据。
我们之后进入map端的数据类型为LongWritable,Text,而map端输出的数据类型为Text,Text(用户,好友),进入reduce之后reduce的输入类型为Text,Text,
最后reduce的输出也是Text,Text(用户,好友列表)。
这样总结:
reduce的输入类型等于reduce输出的数据类型,这样符合Combiner的情况。(这样我们就不需要去自定义数据类型了)
2.2、怎么使用
其实Combiner的本质就是一个reducer,那我们要写Combiner我们就要继承reducer。
下面写一个例子,首先你需要了解我前面写的一个专利引用的例子,才能了解专利文件数据格式。
需求:求这个专利以及这个专利它引用了哪些专利。
import com.briup.bd1702.hadoop.mapred.utils.PatentRecordParser;
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
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.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner; public class PatentReferenceWithCombiner_0010 extends Configured implements Tool{
public static class PatentReferenceMapper extends Mapper<LongWritable,Text,Text,Text>{
private PatentRecordParser parser=new PatentRecordParser();
private Text key=new Text();
private Text value=new Text();
@Override
protected void map(LongWritable key,Text value,Context context) throws IOException, InterruptedException{
parser.parse(value);
if(parser.isValid()){
this.key.set(parser.getPatentId());
this.value.set(parser.getRefPatentId());
context.write(this.key,this.value);
}
}
} public static class PatentReferenceReducer extends Reducer<Text,Text,Text,Text>{
private Text value=new Text();
@Override
protected void reduce(Text key,Iterable<Text> values,Context context) throws IOException, InterruptedException{
StringBuffer refPatentIds=null; for(Text value:values){
refPatentIds.append(value.toString()+",");
}
this.value.set(refPatentIds.toString());
context.write(key,value);
}
} @Override
public int run(String[] args) throws Exception{
Configuration conf=getConf();
Path input=new Path(conf.get("input"));
Path output=new Path(conf.get("output"));
// 构建Job对象,并设置驱动类名和Job名,用于提交作业
Job job=Job.getInstance(conf,this.getClass().getSimpleName());
job.setJarByClass(this.getClass()); // 给Job设置Mapper类以及map方法输出的键值类型
job.setMapperClass(PatentReferenceMapper.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class); // 给Job设置Reducer类及reduce方法输出的键值类型
job.setReducerClass(PatentReferenceReducer.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class); // 设置文件的读取方式,文本文件;输出方式,文本文件
job.setInputFormatClass(TextInputFormat.class);
job.setOutputFormatClass(TextOutputFormat.class); // 给Job是定输入文件的路径和输出结果的路径
TextInputFormat.addInputPath(job,input);
TextOutputFormat.setOutputPath(job,output); // 设置Combiner
job.setCombinerClass(PatentReferenceReducer.class); return job.waitForCompletion(true)?:;
} public static void main(String[] args) throws Exception{
System.exit(ToolRunner.run(new P00010_PatentReferenceWithCombiner_0010(),args));
}
}
PatentReferenceWithCombiner_0010
注意:
1)我们在作业配置中设置Mapper和Reducer,所以我们的Combiner也需要设置的:
2)在这个例子中的Reducer直接就可以当作Combiner去使用。(Combiner的本质就是一个reducer)
2.3、利用Combiner计算每一年的平均气温
1)分析
如果我们不用Cobiner的时候,map输出是(年份,温度),进入reduce中的集合就是这一年中所有的温度值。我们在设置一个变量来叠加一下我们有多少个这样的温度。
然后把所有的温度加起来除以遍历的个数。这是正常情况下!
如果我们利用Combiner计算每一年的平均气温的时候,我们在map端先算一次平均温度,然后到reduce计算一个总的平均气温。
从上图来说,我们看出来虽然满足数据输入的键值类型和数据输出的键值类型一样的reduce,但是这是不符合我们的数学逻辑。
分析上图:我们不可能那把每个平均值拿出来除以个数吧,这样做是错误的。
2)解决
上图分析:我们可以把温度和个数组合起来,自定义一个数据类型(AV)。
注意:我们Combiner和Reduce的数据输入和输出不一样,所以程序中的Reduce就不能作为Reduce了,
我们需要单独去编写一个Combiner,但是我们注意到Combiner和Reduce的实现(算法或应用程序内容)是一模一样的。
3)代码实现Combiner计算每一年的平均气温
第一:写一个AverageValue类
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import org.apache.hadoop.io.DoubleWritable;
import org.apache.hadoop.io.VIntWritable;
import org.apache.hadoop.io.Writable; class AverageValue implements Writable{ private VIntWritable num;
private DoubleWritable avgValue; AverageValue(){
num=new VIntWritable();
avgValue=new DoubleWritable();
} AverageValue(AverageValue av){
num=new VIntWritable(av.num.get());
avgValue=new DoubleWritable(av.avgValue.get());
} @Override
public void write(DataOutput out) throws IOException{
num.write(out);
avgValue.write(out);
} @Override
public void readFields(DataInput in) throws IOException{
num.readFields(in);
avgValue.readFields(in);
} public void set(int num,double avgValue){
this.num.set(num);
this.avgValue.set(avgValue);
} public void set(VIntWritable num,DoubleWritable avgValue){
set(num.get(),avgValue.get());
} public VIntWritable getNum(){
return num;
} public void setNum(VIntWritable num){
this.num=num;
} public DoubleWritable getAvgValue(){
return avgValue;
} public void setAvgValue(DoubleWritable avgValue){
this.avgValue=avgValue;
} @Override
public String toString(){
return "AverageValue{"+"num="+num+", avgValue="+avgValue+'}';
}
AverageValue
第二:实现
import com.briup.bd1702.hadoop.mapred.utils.WeatherRecordParser;
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.DoubleWritable;
import org.apache.hadoop.io.IntWritable;
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.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner; public class AverageTemperatureWithCombiner_0010 extends Configured implements Tool{ static class AverageTemperatureWithCombinerMapper
extends Mapper<LongWritable,Text,
IntWritable,AverageValue>{ private WeatherRecordParser parser=new WeatherRecordParser();
private IntWritable year=new IntWritable();
private AverageValue value=new AverageValue();
@Override
protected void map(LongWritable key,Text value,Context context) throws IOException, InterruptedException{
parser.parse(value);
if(parser.isValid()){
this.year.set(parser.getYear());
this.value.set(,
parser.getTemperature());
context.write(this.year,this.value);
}
}
} static class AverageTemperatureWithCombinerCombiner
extends Reducer<IntWritable,AverageValue,
IntWritable,AverageValue>{ private AverageValue value=new AverageValue(); @Override
protected void reduce(IntWritable key,
Iterable<AverageValue> values,Context context) throws IOException, InterruptedException{
int sum=;
double value=;
for(AverageValue av:values){
sum+=av.getNum().get();
value+=av.getAvgValue().get()
*av.getNum().get();
}
this.value.set(sum,value/sum);
context.write(key,this.value);
}
} static class AverageTemperatureWithCombinerReducer
extends Reducer<IntWritable,AverageValue,IntWritable,DoubleWritable>{
private DoubleWritable value=new DoubleWritable();
@Override
protected void reduce(IntWritable key,Iterable<AverageValue> values,Context context) throws IOException, InterruptedException{
int sum=;
double value=;
for(AverageValue av:values){
sum+=av.getNum().get();
value+=av.getAvgValue().get()*
av.getNum().get();
}
this.value.set(value/sum);
context.write(key,this.value);
}
} @Override
public int run(String[] args) throws Exception{
Configuration conf=getConf();
Path input=new Path(conf.get("input"));
Path output=new Path(conf.get("output")); Job job=Job.getInstance(conf,this.getClass().getSimpleName());
job.setJarByClass(this.getClass()); job.setMapperClass(AverageTemperatureWithCombinerMapper.class);
job.setMapOutputKeyClass(IntWritable.class);
job.setMapOutputValueClass(AverageValue.class); job.setReducerClass(AverageTemperatureWithCombinerReducer.class);
job.setOutputKeyClass(IntWritable.class);
job.setOutputValueClass(DoubleWritable.class); job.setCombinerClass(AverageTemperatureWithCombinerCombiner.class); job.setInputFormatClass(TextInputFormat.class);
job.setOutputFormatClass(TextOutputFormat.class); TextInputFormat.addInputPath(job,input);
TextOutputFormat.setOutputPath(job,output); return job.waitForCompletion(true)?:;
} public static void main(String[] args) throws Exception{
System.exit(ToolRunner.run(new P00030_AverageTemperatureWithCombiner_0010(),args));
}
}
AverageTemperatureWithCombiner_0010
2.4、计算每一年每个气象站的平均温度
1)分析
我们的key可以有两种方式:
使用一个Text和气象站拼接起来作为key,来计算平均温度。
我们可以创建一个数据类型,使用年份和气象站形成一个联合的key(联合腱),我们就写一个YeayStation,对于YearStation既要序列化又要可比较大小要实现WritableComparable<T>。
Hadoop的hash值用来干什么的?
我们需要使用hash值是因为在数据分区的时候,也就是确定哪个数据进入哪个reduce的时候。需要通过hashCode和reduce个数取余的结果确定进入哪个reduce。(IntWritable的默认hash值是它代表int类型数字的本身)
所以说数据分区主要是用的HashCode(key的值得hashCode)。
需要比较大小是因为进入同一个reduce的多组数据谁先进入,要比较它key值得大小。谁小谁先进入。
那我们这个复合键需不要重写hashCode和equals方法?
如果我们不去重写的话,我们使用的是Object的hashCode()方法。当我们一个YearStation对象重复去使用的时候,所有的hashCode都一样。
所以我们还是尽可能的去重写hashCode和equals方法。我们需要year和stationId同时参与分区,那我们重写的hashcode同时和这两个参数有关系。
2)代码实现
第一:编写YearStation类(联合腱)
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.io.WritableComparable; public class YearStation
implements WritableComparable<YearStation>{
private IntWritable year;
private Text stationId; public YearStation(){
year=new IntWritable();
stationId=new Text();
} /*
复制构造器
*/
public YearStation(YearStation ys){
year=new IntWritable(ys.year.get());
stationId=new Text(ys.stationId.toString());
} public void set(YearStation ys){
year=new IntWritable(ys.year.get());
stationId=new Text(ys.stationId.toString());
} public void set(IntWritable year,Text stationId){
this.year=new IntWritable(year.get());
this.stationId=new Text(stationId.toString());
} public void set(int year,String stationId){
this.year=new IntWritable(year);
this.stationId=new Text(stationId);
} @Override
public int compareTo(YearStation o){
int yearComp=year.compareTo(o.year);
int stationIdComp=stationId.compareTo(o.stationId);
return yearComp!=?yearComp:stationIdComp;
} @Override
public void write(DataOutput out) throws IOException{
year.write(out);
stationId.write(out);
} @Override
public void readFields(DataInput in) throws IOException{
year.readFields(in);
stationId.readFields(in);
} @Override
public boolean equals(Object o){
if(this==o) return true;
if(!(o instanceof YearStation)) return false; YearStation that=(YearStation)o; if(!year.equals(that.year)) return false;
return stationId.equals(that.stationId);
} @Override
public int hashCode(){
int result=year.hashCode();
result=*result+stationId.hashCode();
return Math.abs(result);
} @Override
public String toString(){
return year+"\t"+stationId;
}
}
YearStation
注意:在这个需求中,我们需要重写toString()方法,因为我们这个键最后要输出到HDFS中的结果文件中去的。如果不重写可能是一个YearStation的地址。
我们知道reduce做输出最后产生的就是结果文件,那么reduce输出的key和value以什么分割的?其实就是制表符("\t")。所以toString()方法中我们也用这个
第二:实现计算每一年每个气象站的平均温度
import com.briup.bd1702.hadoop.mapred.utils.WeatherRecordParser;
import com.briup.bd1702.hadoop.mapred.utils.YearStation;
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.DoubleWritable;
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.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner; public class AverageTemperatureByYearStationWithCombiner_0010 extends Configured implements Tool{ static class AvgTempByYSWithCombMapper extends Mapper<LongWritable,Text,YearStation,AverageValue>{
private YearStation ys=new YearStation();
private AverageValue av=new AverageValue();
private WeatherRecordParser parser=new WeatherRecordParser();
@Override
protected void map(LongWritable key,Text value,Context context) throws IOException, InterruptedException{
parser.parse(value);
if(parser.isValid()){
ys.set(parser.getYear(),parser.getStationId());
av.set(,parser.getTemperature());
context.write(ys,av);
}
}
} static class AvgTempByYSWithCombCombiner extends Reducer<YearStation,AverageValue,YearStation,AverageValue>{
private AverageValue av=new AverageValue();
@Override
protected void reduce(YearStation key,Iterable<AverageValue> values,Context context) throws IOException, InterruptedException{
int sum=;
double count=0.0;
for(AverageValue av:values){
sum+=av.getNum().get();
count+=av.getAvgValue().get()*av.getNum().get();
}
av.set(sum,count/sum);
context.write(key,av);
}
} static class AvgTempByYSWithCombReducer extends Reducer<YearStation,AverageValue,YearStation,DoubleWritable>{
private DoubleWritable result=new DoubleWritable();
@Override
protected void reduce(YearStation key,Iterable<AverageValue> values,Context context) throws IOException, InterruptedException{
int sum=;
double count=;
for(AverageValue av:values){
sum+=av.getNum().get();
count+=av.getAvgValue().get()*av.getNum().get();
}
result.set(count/sum);
context.write(key,result);
}
} @Override
public int run(String[] args) throws Exception{
Configuration conf=getConf();
Path input=new Path(conf.get("input"));
Path output=new Path(conf.get("output")); Job job=Job.getInstance(conf,this.getClass().getSimpleName());
job.setJarByClass(this.getClass()); job.setMapperClass(AvgTempByYSWithCombMapper.class);
job.setMapOutputKeyClass(YearStation.class);
job.setMapOutputValueClass(AverageValue.class); job.setCombinerClass(AvgTempByYSWithCombCombiner.class); job.setReducerClass(AvgTempByYSWithCombReducer.class);
job.setOutputKeyClass(YearStation.class);
job.setOutputValueClass(DoubleWritable.class); job.setInputFormatClass(TextInputFormat.class);
job.setOutputFormatClass(TextOutputFormat.class); TextInputFormat.addInputPath(job,input);
TextOutputFormat.setOutputPath(job,output); return job.waitForCompletion(true)?:;
} public static void main(String[] args) throws Exception{
System.exit(ToolRunner.run(new P00050_AverageTemperatureByYearStationWithCombiner_0010(),args));
}
}
VAverageTemperatureByYearStationWithCombiner_0010
喜欢就推荐哦!
Hadoop(十六)之使用Combiner优化MapReduce的更多相关文章
- Hadoop(六)MapReduce的入门与运行原理
一 MapReduce入门 1.1 MapReduce定义 Mapreduce是一个分布式运算程序的编程框架,是用户开发“基于hadoop的数据分析应用”的核心框架: Mapreduce核心功能是将用 ...
- Hadoop (六):MapReduce基本使用
MapReduce原理 背景 因为如果要对海量数据进行计算,计算机的内存可能会不够. 因此可以把海量数据切割成小块多次计算. 而分布式系统可以把小块分给多态机器并行计算. MapReduce概述 Ma ...
- hadoop项目之求出每年二月的最高气温(Combiner优化)
hadoop项目之求出每年二月的最高气温(Combiner优化) 一.项目结构 一.java实现随机生成日期和气温 package com.shujia.weather; import java.io ...
- 我的MYSQL学习心得(十六) 优化
我的MYSQL学习心得(十六) 优化 我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(三) 查看字段长度 我的MYSQL学习心得(四) 数据 ...
- [大牛翻译系列]Hadoop(15)MapReduce 性能调优:优化MapReduce的用户JAVA代码
6.4.5 优化MapReduce用户JAVA代码 MapReduce执行代码的方式和普通JAVA应用不同.这是由于MapReduce框架为了能够高效地处理海量数据,需要成百万次调用map和reduc ...
- 十六款值得关注的NoSQL与NewSQL数据库--转载
原文地址:http://tech.it168.com/a2014/0929/1670/000001670840_all.shtml [IT168 评论]传统关系型数据库在诞生之时并未考虑到如今如火如荼 ...
- Hadoop十年解读与发展预测
编者按:Hadoop于2006年1月28日诞生,至今已有10年,它改变了企业对数据的存储.处理和分析的过程,加速了大数据的发展,形成了自己的极其火爆的技术生态圈,并受到非常广泛的应用.在2016年Ha ...
- Hadoop阅读笔记(二)——利用MapReduce求平均数和去重
前言:圣诞节来了,我怎么能虚度光阴呢?!依稀记得,那一年,大家互赠贺卡,短短几行字,字字融化在心里:那一年,大家在水果市场,寻找那些最能代表自己心意的苹果香蕉梨,摸着冰冷的水果外皮,内心早已滚烫.这一 ...
- Web 前端开发人员和设计师必读精华文章【系列二十六】
<Web 前端开发精华文章推荐>2014年第5期(总第26期)和大家见面了.梦想天空博客关注 前端开发 技术,分享各类能够提升网站用户体验的优秀 jQuery 插件,展示前沿的 HTML5 ...
随机推荐
- Asp.Net Web API(三)
Routing Tables路由表 在Asp.Net Web API中,一个控制器就是一个处理HTTP请求的类,控制器的public方法就被叫做action方法或简单的Action.当Web API接 ...
- mysql主从备份+keepalived自动切换
数据库这一层需要做到避免单点故障可以是主从备份和主主备份,主主备份可能有性能损耗和数据同步的问题.这里记录下主从备份, mysql进行备份之前确保mysql的版本是一样的,我这里用的都是mysql5. ...
- 4. sudo,PATH环境变量,修改字符集,ntpserver,加大文件描述符,隐藏内核版本,锁定关键系统文件
1 命令: visudo 98gg 98行 yy 复制当前行 p ...
- Data Base mongodb driver2.5环境注意事项
mongodb driver2.5环境注意事项 一.问题: 如果使用vs2012开发就会报这个错误: 未能加载文件或程序集“System.Runtime.InteropServices.Runtime ...
- [置顶]
xamarin Tablayout+Viewpager+Fragment顶部导航栏
最近几天不忙,所以把项目中的顶部导航栏的实现归集一下.android中使用TabLayout+ViewPager+Fragment制作顶部导航非常常见,代码实现也比较简单.当然我这个导航栏是基于xam ...
- 程序员的自我救赎---11.1:RPC接口使用规范
<前言> (一) Winner2.0 框架基础分析 (二)PLSQL报表系统 (三)SSO单点登录 (四) 短信中心与消息中心 (五)钱包系统 (六)GPU支付中心 (七)权限系统 (八) ...
- js-使用JavaScript、jQuery两种方式实现全选/全不选
html代码 <input type='checkbox' value="10" name="frust"/>苹果10元 <br/> & ...
- getComputedStyle与currentStyle获取样式(style/class)
今天看jQuery源码CSS部分,里面用到了currentStyle和getComputedStyle来获取外部样式. 因为elem.style.width只能获取elem的style属性里的样式,无 ...
- php一篇入门
<?php header("Content-type: text/html; charset=utf-8");//设置编码也可以通过html中的 head中的 <met ...
- JSP EL隐含对象
JSP 内置对象 JSP EL隐含对象 描述 page pageScope page 作用域 request requestScope request 作用域 session sessionScope ...