mapreduce 计算excel_MapReduce的分析4---表格基于关键字Join操作的实现

本文介绍了一种使用MapReduce实现基于关键字的表格Join操作的方法。以人员和地址信息为例,通过MapReduce程序将人员信息与地址信息进行Join,将人员的地址ID转换为地址名称。在Map阶段,根据数据类型创建Record对象并设置key-value对,Reduce阶段则进行真正的Join操作,将相同地址ID的人员信息与地址信息匹配。程序以Java实现,包括Record类、Join主程序、Mapper和Reducer的详细代码。
摘要由CSDN通过智能技术生成

由于最近忙于实验室的工作,没有更新MR,把我前些日子看到一个篇好的文章分享给大家。

对于一个大数据的分析应用,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->value

list数据结构中传送到reduce中进行处理.在reduce过程中,由于key是地址的ID,所以value

list中只有一个是地址信息,其他的都是人员信息,因此,找到该地址信息后,其他的人员信息的地址就是该地址所指定的地址名称.

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

go.

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

import java.io.DataInput;

import java.io.DataOutput;

import java.io.IOException;

import org.apache.hadoop.io.WritableComparable;

public class Record implements WritableComparable

{

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

String empName="";

String empId="";

String locId="";

String locationName="";

public Record(){

super();

}

public Record(Record

record){

this.type

= record.type;

this.empName

= record.empName;

this.empId

= record.empId;

this.locId

= record.locId;

this.locationName =

record.locationName;

}

public String

toString(){

if(type == 1)

return empId+","+empName+","+locationName;

else if(type

== 2)

return locId+","+locationName;

return "uninit data!";

}

public void readFields(DataInput in) throws IOException

{

type =

in.readInt();

empName =

in.readUTF();

empId =

in.readUTF();

locId =

in.readUTF();

locationName =

in.readUTF();

}

public void write(DataOutput out) throws IOException

{

out.writeInt(type);

out.writeUTF(empName);

out.writeUTF(empId);

out.writeUTF(locId);

out.writeUTF(locationName);

}

public int compareTo(Object arg0) {

return 0;

}

}

面的Record的实现了WritableComparable,对于Mapreduce的中间结果类来说,必须要实现Writable,从而在map完

成输出中间结果时能够将中间结果写入到运行job的node文件系统中,至于Comparable接口的实现,对于作为Key的中间结果来说需要实现该接

口,从而能够完成基于key的排序功能.

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

import org.apache.hadoop.fs.FileSystem;

import org.apache.hadoop.fs.Path;

import org.apache.hadoop.io.LongWritable;

import org.apache.hadoop.io.SequenceFile;

import org.apache.hadoop.io.Text;

import org.apache.hadoop.mapred.FileInputFormat;

import org.apache.hadoop.mapred.FileOutputFormat;

import org.apache.hadoop.mapred.JobClient;

import org.apache.hadoop.mapred.JobConf;

import org.apache.hadoop.mapred.SequenceFileOutputFormat;

public class Join

{

public static void main(String[]

args) throws Exception {

// TODO Auto-generated

method stub

JobConf conf = new JobConf(Join.class);

conf.setJobName("Join");

FileSystem fstm =

FileSystem.get(conf);

Path outDir = new

Path("/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,

new Path(

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

FileOutputFormat.setOutputPath(conf,

outDir);

JobClient.runJob(conf);

Path outPutFile = new Path(outDir, "part-00000");

SequenceFile.Reader reader =

new SequenceFile.Reader(fstm,

outPutFile,

conf);

org.apache.hadoop.io.Text numInside =

new Text();

LongWritable numOutside =

new LongWritable();

while (reader.next(numOutside, numInside))

{

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

" "

+ numOutside.toString());

}

reader.close();

}

}

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

import java.io.IOException;

import org.apache.hadoop.mapred.MapReduceBase;

import org.apache.hadoop.mapred.Mapper;

import org.apache.hadoop.mapred.OutputCollector;

import org.apache.hadoop.mapred.Reporter;

import org.apache.hadoop.io.*;

public class JoinMapper extends MapReduceBase

implements Mapper

LongWritable, Record> {

public void map(LongWritable key, Text value,

OutputCollector

Record> output, Reporter reporter)

throws IOException

{

String line = value.toString();

String[] values =

line.split(",");

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

Record reco = new Record();

reco.locId

= values[0];

reco.type

= 2;

reco.locationName =

values[1];

output.collect(new LongWritable(Long.parseLong(values[0])),

reco);

}else{

Record reco = new Record();

reco.empId

= values[0];

reco.empName

= values[1];

reco.locId

= values[2];

reco.type

= 1;

output.collect(new LongWritable(Long.parseLong(values[2])),

reco);

}

}

}

于maper来说,就是从输入文件中读取相应数据构造key->value(地址id->地址或者人员对象)的数据对,并交给hadoop框

架完成shuffle等工作.经过hadoop框架完成suffle之后便会将具有想同地址ID的人员信息以及地址信息交给reducer来进行处理.

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

import java.io.IOException;

import java.util.Iterator;

import java.util.List;

import java.util.Vector;

import org.apache.hadoop.io.LongWritable;

import org.apache.hadoop.io.Text;

import org.apache.hadoop.mapred.MapReduceBase;

import org.apache.hadoop.mapred.OutputCollector;

import org.apache.hadoop.mapred.Reducer;

import org.apache.hadoop.mapred.Reporter;

public class JoinReducer

extends MapReduceBase implements

Reducer

LongWritable, Text> {

public void reduce(LongWritable key,

Iterator values,

OutputCollector

Text> output,

Reporter reporter) throws IOException

{

System.out.println("reducer for

key "+key.toString());

Record thisLocation= new Record();

List

employees= new Vector();

while (values.hasNext()){

Record reco = values.next();

if(reco.type == 2){

//2 is the location

thisLocation = new Record(reco);

//thisLocation =

reco;

System.out.println("location is

"+

thisLocation.locationName);

}else{ //1 is employee

Record recoClone = new Record(reco);

employees.add(recoClone);

//employess.add(reco);

System.out.println(" employess

"+

reco.toString());

}

}

for(Record e :

employees){

e.locationName =

thisLocation.locationName;

output.collect(new LongWritable(0), new Text(e.toString()));

}

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

}

}

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

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

一,在while

(values.hasNext())的循环中的thisLocation =

new Record(reco)以及Record recoClone =

new Record(reco)语句,我们不能直接保存reducer的迭代器中的对象,因为迭代器中每次返回的对象都是同一个Object,但是具有不同的值.注意,一定要注意.

二,

这个是一个比较蹩脚的reduce实现,从程序中我们可以看到.我们用了一个List来保存某个地址ID的所有人员信息,对于一个非常巨大的应用来说,某

个地址ID可能具有大于List长度的人员信息,这就会造成List溢出.下次对该程序进行优化从而能够避免该现象.

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

$ ./hadoop fs -cat

input/join/names

1,张三,1

2,李四,2

3,王五,1

4,赵六,3

5,马七,3

$ ./hadoop fs -cat

input/join/locations

1,北京

2,上海

3,广州

运行程序:

1,张三,北京 0

3,王五,北京 0

2,李四,上海 0

4,赵六,广州 0

5,马七,广州 0

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值