基于mapreduce的Hadoop join实现分析.docx

上传人:b****8 文档编号:10883104 上传时间:2023-02-23 格式:DOCX 页数:21 大小:22.05KB
下载 相关 举报
基于mapreduce的Hadoop join实现分析.docx_第1页
第1页 / 共21页
基于mapreduce的Hadoop join实现分析.docx_第2页
第2页 / 共21页
基于mapreduce的Hadoop join实现分析.docx_第3页
第3页 / 共21页
基于mapreduce的Hadoop join实现分析.docx_第4页
第4页 / 共21页
基于mapreduce的Hadoop join实现分析.docx_第5页
第5页 / 共21页
点击查看更多>>
下载资源
资源描述

基于mapreduce的Hadoop join实现分析.docx

《基于mapreduce的Hadoop join实现分析.docx》由会员分享,可在线阅读,更多相关《基于mapreduce的Hadoop join实现分析.docx(21页珍藏版)》请在冰豆网上搜索。

基于mapreduce的Hadoop join实现分析.docx

基于mapreduce的Hadoopjoin实现分析

基于mapreduce的Hadoopjoin实现分析

标签:

hadoopmapreducejoin

2009-11-2021:

50

对于一个大数据的分析应用,join是必不可少的一项功能.现在很多构建与hadoop之上的应用,如Hive,PIG等在其内部实现了join程序,可以通过很简单的sql语句或者数据操控脚本完成相应的Join工作.那么join应该如何实现呢?

今天我们就对join做一个简单的实现.

我们来看一个例子,现在有两组数据:

一组为单位人员信息,如下:

人员ID人员名称地址ID

1张三1

2李四2

3王五1

4赵六3

5马七3

另外一组为地址信息:

地址ID地址名称

1北京

2上海

3广州

这里给出了一个很简单的例子,而且数据量很小,就这么用眼睛就能看过来的几行,当然,实际的情况可能是几十万上百万甚至上亿的数据量.要实现的功能很简单,就是将人员信息与地址信息进行join,将人员的地址ID完善成为地址名称.对于Hadoop文件系统的应用,目前看来,很多数据的存储都是基于文本的,而且都是将数据放在一个文件目录中进行处理.因此我们这里也采用这种模式来完成.

对于mapreduce程序来说,最主要的就是将要做的工作转化为map以及reduce两个部分.我们可以将地址以及人员都采用同样的数据结构来存储,通过一个flag标志来指定该数据结构里面存储的是地址信息还是人员信息.经过map后,使用地址ID作为key,将所有的具有相同地址的地址信息和人员信息放入一个key->valuelist数据结构中传送到reduce中进行处理.在reduce过程中,由于key是地址的ID,所以valuelist中只有一个是地址信息,其他的都是人员信息,因此,找到该地址信息后,其他的人员信息的地址就是该地址所指定的地址名称.

OK,我们的join算法基本搞定啦.剩下就是编程实现了,let’sgo.

上面提到了存储人员和地址信息的数据结构,可以说这个数据结构是改程序的重要的数据载体之一.我们先来看看该数据结构:

importjava.io.DataInput;

importjava.io.DataOutput;

importjava.io.IOException;

importorg.apache.hadoop.io.WritableComparable;

publicclassRecordimplementsWritableComparable{

inttype;//数据类型的定义,1为人员,2为地址

StringempName="";

StringempId="";

StringlocId="";

StringlocationName="";

publicRecord(){

super();

}

publicRecord(Recordrecord){

this.type=record.type;

this.empName=record.empName;

this.empId=record.empId;

this.locId=record.locId;

this.locationName=record.locationName;

}

publicStringtoString(){

if(type==1)

returnempId+","+empName+","+locationName;

elseif(type==2)

returnlocId+","+locationName;

return"uninitdata!

";

}

publicvoidreadFields(DataInputin)throwsIOException{

type=in.readInt();

empName=in.readUTF();

empId=in.readUTF();

locId=in.readUTF();

locationName=in.readUTF();

}

publicvoidwrite(DataOutputout)throwsIOException{

out.writeInt(type);

out.writeUTF(empName);

out.writeUTF(empId);

out.writeUTF(locId);

out.writeUTF(locationName);

}

publicintcompareTo(Objectarg0){

return0;

}

}

上面的Record的实现了WritableComparable,对于Mapreduce的中间结果类来说,必须要实现Writable,从而在map完成输出中间结果时能够将中间结果写入到运行job的node文件系统中,至于Comparable接口的实现,对于作为Key的中间结果来说需要实现该接口,从而能够完成基于key的排序功能.

接下来是Join的主程序,就是mapreduce的主程序.基本的主程序如下:

importorg.apache.hadoop.fs.FileSystem;

importorg.apache.hadoop.fs.Path;

importorg.apache.hadoop.io.LongWritable;

importorg.apache.hadoop.io.SequenceFile;

importorg.apache.hadoop.io.Text;

importorg.apache.hadoop.mapred.FileInputFormat;

importorg.apache.hadoop.mapred.FileOutputFormat;

importorg.apache.hadoop.mapred.JobClient;

importorg.apache.hadoop.mapred.JobConf;

importorg.apache.hadoop.mapred.SequenceFileOutputFormat;

publicclassJoin{

publicstaticvoidmain(String[]args)throwsException{

//TODOAuto-generatedmethodstub

JobConfconf=newJobConf(Join.class);

conf.setJobName("Join");

FileSystemfstm=FileSystem.get(conf);

PathoutDir=newPath("/Users/hadoop/outputtest");

fstm.delete(outDir,true);

conf.setOutputFormat(SequenceFileOutputFormat.class);

conf.setMapOutputValueClass(Record.class);

conf.setOutputKeyClass(LongWritable.class);

conf.setOutputValueClass(Text.class);

conf.setMapperClass(JoinMapper.class);

conf.setReducerClass(JoinReducer.class);

FileInputFormat.setInputPaths(conf,newPath(

"/user/hadoop/input/join"));

FileOutputFormat.setOutputPath(conf,outDir);

JobClient.runJob(conf);

PathoutPutFile=newPath(outDir,"part-00000");

SequenceFile.Readerreader=newSequenceFile.Reader(fstm,outPutFile,

conf);

org.apache.hadoop.io.TextnumInside=newText();

LongWritablenumOutside=newLongWritable();

while(reader.next(numOutside,numInside)){

System.out.println(numInside.toString()+""

+numOutside.toString());

}

reader.close();

}

}

程序主体很简单,开始将输出目录删除,中间进行一系列的JobConf设定工作,将输出格式设为SequenceFile,最后读出程序结果到控制台.接下来我们看看Mapper的实现:

importjava.io.IOException;

importorg.apache.hadoop.mapred.MapReduceBase;

importorg.apache.hadoop.mapred.Mapper;

importorg.apache.hadoop.mapred.OutputCollector;

importorg.apache.hadoop.mapred.Reporter;

importorg.apache.hadoop.io.*;

publicclassJoinMapperextendsMapReduceBase

implementsMapper{

publicvoidmap(LongWritablekey,Textvalue,

OutputCollectoroutput,Reporterreporter)

throwsIOException{

Stringline=value.toString();

String[]values=line.split(",");

if(values.length==2){//这里使用记录的长度来区别地址信息与人员信息,当然可以通过其他方式(如文件名等)来实现

Recordreco=newRecord();

reco.locId=values[0];

reco.type=2;

reco.locationName=values[1];

output.collect(newLongWritable(Long.parseLong(values[0])),reco);

}else{

Recordreco=newRecord();

reco.empId=values[0];

reco.empName=values[1];

reco.locId=values[2];

reco.type=1;

output.collect(newLongWritable(Long.parseLong(values[2])),reco);

}

}

}

对于maper来说,就是从输入文件中读取相应数据构造key->value(地址id->地址或者人员对象)的数据对,并交给hadoop框架完成shuffle等工作.经过hadoop框架完成suffle之后便会将具有想同地址ID的人员信息以及地址信息交给reducer来进行处理.

好啦,剩下就是最后一步了,其实也是最重要的一步就是reduce端的join工作了.还是来看看代码吧:

importjava.io.IOException;

importjava.util.Iterator;

importjava.util.List;

importjava.util.Vector;

importorg.apache.hadoop.io.LongWritable;

importorg.apache.hadoop.io.Text;

importorg.apache.hadoop.mapred.MapReduceBase;

importorg.apache.hadoop.mapred.OutputCollector;

importorg.apache.hadoop.mapred.Reducer;

importorg.apache.hadoop.mapred.Reporter;

publicclassJoinReducerextendsMapReduceBaseimplements

Reducer{

publicvoidreduce(LongWritablekey,Iteratorvalues,

OutputCollectoroutput,

Reporterreporter)throwsIOException{

System.out.println("reducerforkey"+key.toString());

RecordthisLocation=newRecord();

Listemployees=newVector();

while(values.hasNext()){

Recordreco=values.next();

if(reco.type==2){//2isthelocation

thisLocation=newRecord(reco);

//thisLocation=reco;

System.out.println("locationis"+thisLocation.locationName);

}else{//1isemployee

RecordrecoClone=newRecord(reco);

employees.add(recoClone);

//employess.add(reco);

System.out.println("employess"+reco.toString());

}

}

for(Recorde:

employees){

e.locationName=thisLocation.locationName;

output.collect(newLongWritable(0),newText(e.toString()));

}

System.out.println("+++++++++++++++");

}

}

在reducer端,我们先构造了一个地址对象,thisLocation用来保存地址信息.在reducer的迭代器values中,如果某个value是地址,就将其保存到thisLocation中.否则就将人员信息加入到List中以供后面打印.

这个reducer中有两点需要非常注意:

一,在while(values.hasNext())的循环中的thisLocation=newRecord(reco)以及RecordrecoClone=newRecord(reco)语句,我们不能直接保存reducer的迭代器中的对象,因为迭代器中每次返回的对象都是同一个Object,但是具有不同的值.注意,一定要注意.

二,这个是一个比较蹩脚的reduce实现,从程序中我们可以看到.我们用了一个List来保存某个地址ID的所有人员信息,对于一个非常巨大的应用来说,某个地址ID可能具有大于List长度的人员信息,这就会造成List溢出.下次对该程序进行优化从而能够避免该现象.

好啦,看看数据和程序的运行结果吧!

$./hadoopfs-catinput/join/names

1,张三,1

2,李四,2

3,王五,1

4,赵六,3

5,马七,3

$./hadoopfs-catinput/join/locations

1,北京

2,上海

3,广州

运行程序:

09/11/2021:

44:

09WARNmapred.JobClient:

UseGenericOptionsParserforparsingthearguments.ApplicationsshouldimplementToolforthesame.

09/11/2021:

44:

10INFOmapred.FileInputFormat:

Totalinputpathstoprocess:

2

09/11/2021:

44:

11INFOmapred.JobClient:

Runningjob:

job_200911202139_0001

09/11/2021:

44:

12INFOmapred.JobClient:

map0%reduce0%

09/11/2021:

44:

24INFOmapred.JobClient:

map33%reduce0%

09/11/2021:

44:

26INFOmapred.JobClient:

map66%reduce0%

09/11/2021:

44:

28INFOmapred.JobClient:

map100%reduce0%

09/11/2021:

44:

35INFOmapred.JobClient:

map100%reduce100%

09/11/2021:

44:

36INFOmapred.JobClient:

Jobcomplete:

job_200911202139_0001

09/11/2021:

44:

37INFOmapred.JobClient:

Counters:

16

09/11/2021:

44:

37INFOmapred.JobClient:

FileSystems

09/11/2021:

44:

37INFOmapred.JobClient:

HDFSbytesread=97

09/11/2021:

44:

37INFOmapred.JobClient:

HDFSbyteswritten=246

09/11/2021:

44:

37INFOmapred.JobClient:

Localbytesread=243

09/11/2021:

44:

37INFOmapred.JobClient:

Localbyteswritten=582

09/11/2021:

44:

37INFOmapred.JobClient:

JobCounters

09/11/2021:

44:

37INFOmapred.JobClient:

Launchedreducetasks=1

上次我们讨论了基于mapreduce的join的实现,在上次讨论的最后,我们对这个实现进行了总结,最主要的问题就是实现的可扩展性,由于在reduce端我们通过一个List数据结构保存了所有的某个外键的对应的所有人员信息,而List的最大值为Integer.MAX_VALUE,所以在数据量巨大的时候,会造成List越界的错误.所以对这个实现的优化显得很有必要.

我们再来看一下这个例子,现在有两组数据:

一组为单位人员信息,如下:

人员ID人员名称地址ID

1张三1

2李四2

3王五1

4赵六3

5马七3

另外一组为地址信息:

地址ID地址名称

1北京

2上海

3广州

结合第一种实现方式,我们看到第一种方式最需要改进的地方就是如果对于某个地址ID的迭代器values,如果values的第一个元素是地址信息的话,那么,我们就不需要缓存所有的人员信息了.如果第一个元素是地址信息,我们读出地址信息后,后来就全部是人员信息,那么就可以将人员的地址置为相应的地址.

现在我们回头看看mapreduce的partition和shuffle的过程,partitioner的主要功能是根据reduce的数量将map输出的结果进行分块,将数据送入到相应的reducer,所有的partitioner都必须实现Partitioner接口并实现getPartition方法,该方法的返回值为int类型,并且取值范围在0-numOfReducer-1,从而能够将map的输出输入到相应的reducer中,对于某个mapreduce过程,Hadoop框架定义了默认的partitioner为HashPartition,该Partitioner使用key的hashCode来决定将该key输送到哪个reducer;shuffle将每个partitioner输出的结果根据key进行group以及排序,将具有相同key的value构成一个valeus的迭代器,并根据key进行排序分别调用开发者定义的reduce方法进行归并.从shuffle的过程我们可以看出key之间需要进行比较,通过比较才能知道某两个key是否相等或者进行排序,因此mapduce的所有的key必须实现comparable接口的compareto()方法从而实现两个key对象之间的比较.

回到我们的问题,我们想要的是将地址信息在排序的过程中排到最前面,前面我们只通过locId进行比较的方法就不够用了,因为其无法标识出是地址表中的数据还是人员表中的数据.因此,我们需要实现自己定义的Key数据结构,完成在想共同locId的情况下地址表更小的需求.由于map的中间结果需要写到磁盘上,因此必须实现writable接口.具体实现如下:

importjava.io.DataInput;

importjava.io.DataOutput;

importjava.io.IOException;

importorg.apache.hadoop.io.WritableComparable;

publicclassRecordKeyimplementsWritableComparable{

intkeyId;

booleanisPrimary;

publicvoidreadFields(DataInputin)throwsIOException{

//TODOAuto-generatedmethodstub

this.keyId=in.readInt();

this.isPrimary=in.readBoolean();

}

publicvoidwrite(DataOutputout)throwsIOException{

//TODOAuto-generatedmethodstub

out.writeInt(keyId);

out.writeBoolean(isPrimary);

}

publicintcompareTo(RecordKeyk){

//TODOAut

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 外语学习 > 其它语言学习

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1