hadoop-Reduce侧联结

来源:互联网 发布:西安云计算 编辑:程序博客网 时间:2024/05/01 18:31

链接不同来源的数据:

Reduce侧的联结、DATAJOIN软件包实现联结、

 

DATAJOIN软件包实现了联结的数据流

其中有3个可供继承和具体化的抽象类:DataJoinMapperBase、DateJoinReducerBase和TaggedMapOutput

在数据流中mapper输出的包带有一个(组)键和一个被标签记录的值。
datajoin包指定(组)键为Text类型,而值(即被标记的记录)为TaggedOutput类型。
TaggedMapOutput是一种用Text标签封装记录的数据类型。它具体实现了getTag()和
setTag(Text tag)方法,并指定抽象方法getDate()。
没有明确要求我们的子类必须包含setData()方法,但我们必须将记录数据传入。
作为mapper的输出,TaggedMapOutput必须为Writable类型。
所以,我们的子类必须实现readFields()和write()方法。

//用于处理所有Writable的记录类型。
public static class TaggedWritable extends TaggedMapOutput{
 
 private Writable data;

 public TaggedWritable(Writable data){
  this.tag = new Text("");
  this.data = data;
 }
 
 public Writable getData(){
  return data;
 }
 ...
}

在联结操作的数据流中,mapper的主要功能时封装一个记录,
使它与其他有相同联结键的记录都被送到相同的reducer。
DataJoinMapperBase执行所有的封装,但这个类为我们的子类指定了3个
可以填充的抽象方法:
protected abstract Text generateInputTag(String inputFile);
//map任务开始时被调用,为这个map任务所处理的所有记录指定一个全局的标签。
//Text类型,我们使用记录的文件名来调用generateInputTag()。
//mapper将接收处理文件的文件名(以字符串形式)作为generateInputTag()的参数。
protected abstract TaggedMapOutput generateTaggedMapOutput(Object value);
protected abstract Text generateGroupKey(TaggedMapOutput aRecord);

我们正在使用这个标签来标识数据源,我们的文件名又是用来反映数据源的
generateInputTag()写为

 protected Text generateInputTag(String inputFile){
  return new Text(inputFile);
 }


如果数据源横跨几个文件(part-0000、part-0001等),
不用完整的文件名作为标签,而是用前缀的方法是:

eg:标签(数据源)可以为短画线(-)前的文件名。
protected Text generateInputTag(String inputFile){
 String datasource = inputFile.split('-')[0];
 return new Text(datasource);
}

之后generateInputTag()的结果存放在DataJoinMapperClass对象的变量inputTag中
如果想再次提及它可把文件名存入DataJoinMapperBase的变量inputFile中。

-------
完成mapper函数的初始化后,为每个记录调用DataJoinMapperBase的map()。
之后它再实现后边的抽象方法。

public void map(Object key,Object value,
  OutputCollector output,Reporter reporter) throws IOException
{
 TaggedMapOutput aRecord = generateTaggedMapOutput(value);
 Text groupKey = generateGroupKey(aRecord);
 output.collect(groupKey,aRecord);
}

TaggedMapOutput具体实现为TagWritable。
GenerateTaggedMapOutput()方法可以返回一个带有任何我们所想要的Text标签
的TaggedWritable。
原则上,在同一文件中,不同的记录可以用不同的标签。
标准情况下,希望标签代表一个数据源,它由早先已由generateInputTag()
计算好并放在this.inputTag中。

protected TaggedMappedOutput generateTaggedMapOutput(Object value){
 TaggedWritable retv = new TaggedWritable((Text),value);
 retv.setTag(this.inputTag);
 return retv;
}
GenerateGroupKey()方法取得被标记的记录(TaggedMapOutput类型),
并返回用作联结的组键。
之后,我们解开有标签的记录,并取CSV格式的值的第一个域作为联结键。

protected Text generateGroupKey(TaggedMapOutput aRecord){
 String line = ((Text)aRecord.getDate()).toString();
 String[] tokens = line.split(",");
 String groupKey = tokens[0];
 return new Text(groupKey);
}

用户能够指定哪些字段为联结键,以及是否记录的分隔符可以为逗号之外的一些字符。
DataJoinMapperBase是一个简单的类,而mapper代码的大部分在我们的子类中。
DataJoinReducerBase是datajoin软件包的核心,它通过执行一个完整的外部联结,
简化编程。
我们的reducer子类只是必须实现combine()方法来筛选掉不需要的组合,以获取所需
的联结操作(内部联结,左外部联结的等)。
在combine()方法中,我们将合并结果格式化为合适的输出格式。

我们为combine()方法提供一个有相同联结键(组)的标记记录的交叉乘的合并。
每个合并的记录要么为2个(意味着在每个有组键的数据源中至少有一个记录),
要么为1个(意味着仅有一个数据源有该联结键)。

protected abstract TaggedMapOutput combine(Object[] tags,Object[] values);

一个合并结果由一个标签数组和一个值的数组来表示。
这两个数组的大小必须保证相同,且等于合并结果中被标记记录的个数。
合并结果中第一个被标记的记录表示为tags[0]和values[0],
第2个为tags[1]和values[1],以此类推。标签是按顺序排列的。

 

由于标签与数据源对应,在两个数据源相连接的典型情况下,
combine()的标签数组长度不会超过2。

eg:
tags = {"Customers","Orders"};
values = {"3,Jose Madriz,281-330-8004","A,12.95,02-Jun-2008"};

  对于内部联结,combine()会忽略那些没有全部标签的合并结果,而返回空值。

  在一个合法的合并中,combine()的作用是将所有值都连接成一个单个记录
  来输出。联结的顺序完全是由combine()决定的。
  内部联结中values[]的长度总是等于可用数据源的个数(典型情况下为两个),
  而且标签总是排好序的。一个合理的选择是循环遍历values[]数组,
  使其按照数据源名称的默认字母顺序进行排序。

  像任何reducer一样,DataJoinReducerBase输出键/值对。
  对于每个合法的合并,键总是为联结键,值为combine()的输出。
  此外,联结键还出现在values[]数组的每个单元中。
  Combine()在连接它们之前,会去掉这些单元中的连接键。
  否则联结键会在输出的一条记录中被多次显示。

  最后,DataJoinReducerBase希望combine()返回TaggedMapOutput。
  至于为什么DataJoinReducerBase忽略在TaggedMapOutput对象中的标签,
  目前尚不清楚原因。

 

//连个reduce侧连接数据的内联结
public class DataJoin extends Configured implements Tool {

 
 public static class MapClass extends DataJoinMapperBase{
  
  protected Text generateInputTag(String inputFile){
   String datasource = inputFile.split("-")[0];
   return new Text(datasource);
  }
  
  protected Text generatedGroupKey(TaggedMapOutput aRecord){
   String line = ((Text)aRecord.getData()).toString();
   String[] tokens = line.split(",");
   String groupKey = tokens[0];
   return new Text(groupKey);
  }
  
  protected TaggedMapOutput generateTaggedMapOutput(Object value){
   TaggedWritable retv = new TaggedWritable((Text)value);
   retv.setTag(this.inputTag);
   return retv;
  }
 }
 
 public static class Reduce extends DataJoinReducerBase{
  
  protected TaggedMapOutput combine(Object[] tags,Object[] values){
   if(tags.length<2) return null;
   String joinedStr = "";
   for(int i=0;i
    if(i>0) joinedStr += ",";
    TaggedWritable tw = (TaggedWritable) values[i];
    String line = ((Text)tw.getData()).toString();
    String[] tokens = line.split(",",2);
    joinedStr += tokens[1];
   }
   TaggedWritable retv = new TaggedWritable(new Text(joinedStr));
   retv.setTag((Text)tags[0]);
   return retv;
  }
 }
 
 
 public static class TaggedWritable extends TaggedMapOutput{
  
  private Writable data;
  
  public TaggedWritable(Writable data){
   this.tag = new Text("");
   this.data = data;
  }
  
  public Writable getData(){
   return data;
  }
  
  public void write(DataOutput out) throws IOException{
   this.tag.write(out);
   this.data.write(out);
  }
  
  public void readFields(DataInput in) throws IOException{
   this.tag.readFields(in);
   this.data.readFields(in);
  }
 }
 @Override
 public int run(String[] args) throws Exception {
  //
  Configuration conf = getConf();
  
  JobConf job = new JobConf(conf,DataJoin.class);
  
  Path in = new Path(args[0]);
  Path out = new Path(args[1]);
  FileInputFormat.setInputPaths(job, in);
  FileOutputFormat.setOutputPath(job, out);
  job.setJobName("DataJoin");
  job.setMapperClass(MapClass.class);
  job.setReducerClass(Reduce.class);
  job.setInputFormat(TextInputFormat.class);
  job.setOutputFormat(TextOutputFormat.class);
  job.setOutputKeyClass(Text.class);
  job.setOutputValueClass(TaggedWritable.class);
  job.set("mapred.textoutputformat.separator", ",");
  JobClient.runJob(job);
  
  return 0;
 }

 public static void main(String[] args) throws Exception{
  //

  int res = ToolRunner.run(new Configuration(),
        new DataJoin(),
        args);
  System.exit(res);
 }

}

0 0