Hadoop中使用datajoin实现reduce侧连接

hadoop中数据文件连接

hadoop实际应用中,经常需要连接来自不同数据源的数据文件,然后在某些属性上进行连接操作。类似于数据库中使用join进行多表连接,例如在Foreign key上进行连接。

在数据库中由于SQL支持join语法,所以实现多表连接只需要写SQL语句即可实现。但是在hadoop中,实现不同数据文件中记录的连接操作,却并没有如此简单。考虑如下两个数据文件。

数据文件customer       1,aaaaaaa,4444444444444                                                                       

                           2,bbbbbbb,5555555555555                                            

                                        3,ccccccc,6666666666666                                                                          

                                        4,ddddddd,7777777777777     

                                                                

数据文件order              3,x,12.01,02-Jun-2009
                                       1,y,12.02,04-Mon-2008
                                        2,z,12.05,05-Oct-2010

                                        3,w,12.08,09-Jan-2012

要实现在两在数据文件第一列 customer id上将数据记录进行连接,连接后输出如下:

                   1,aaaaaaa,4444444444444,y,12.02,04-Mon-2008

result         2,bbbbbbb,5555555555555,z,12.05,05-Oct-2010

                   3,ccccccc,6666666666666,x,12.01,02-Jun-2009

                   3,ccccccc,6666666666666,w,12.08,09-Jan-2012


使用MapReduce实现上述操作

连接的数据流图如下:
                       
               
hadoop中可以使用 datajoin软件包实现上述操作,datajoin软件包在$HADOOP_INSTALL/contrib/datajoin/目录下,在Hadoop官方API中并没有包含该软件包的API。下面列出了使用该软件包的基本知识。
三个基本概念
  • tag   标签 ,由于MapReduce需要处理多个数据源中的数据,所以MapReduce每次处理一条记录的时候需要记住该记录属于哪个数据源,所以每条记录必须打上一个tag
  • datasource  数据源   例如上面的 customer  ,order
  • group key 组键   即连接键,例如上述的customer id
三个需要用户实现的抽象类
  • DataJoinMapperBase  自己实现的Mapper必须继承该类,该类提供了三个抽象方法给子类实现
  • DataJoinReducerBase 自己实现的Reducer必须继承的类,需要实现combine方法,完成记录的连接,可以控制是内连接,外连接等等操作
  • TaggedMapOutput   记录连接过程中,传递的数据类型,该类似包含了一个Text类型的tag变量,子类需要继承该类,实现getData方法

实现代码DataJoin.java
public class DataJoin extends Configured implements Tool { 
    
    public static class MapClass extends DataJoinMapperBase { 
    	
    	@Override
        protected Text generateInputTag(String inputFile) { //tag 给记录添加标签,这里以记录所在的文件名称为记录的标签
            return new Text(inputFile);
        }
        
        @Override
        protected TaggedMapOutput generateTaggedMapOutput(Object value) {//将记录原始值包装成 TaggedMapOutput类型
            TaggedWritable retv = new TaggedWritable((Text) value);
            retv.setTag(this.inputTag); // 上述生generateInputTag方法成的标签被保存在inputTag中
            return retv;
        }
        
        @Override
        protected Text generateGroupKey(TaggedMapOutput aRecord) {//从generateTaggedMapOutput方法生成的每条记录中提取出组键
            String line = ((Text) aRecord.getData()).toString();
            String[] tokens = line.split(",");
            String groupKey = tokens[0];
            return new Text(groupKey);
        }
    }
    
    public static class Reduce extends DataJoinReducerBase {
        
        protected TaggedMapOutput combine(Object[] tags, Object[] values) {//将上述Mapper生成值进行合并
            if (tags.length < 2) return null;  
            String joinedStr = ""; 
            for (int i=0; i<values.length; 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() {   //反射   必须要有默认构造方法
            this.tag = new Text();
        }

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

        public Writable getData() {
            return data;
        }

        public void setData(Writable data) {
            this.data = data;
        }

        public void write(DataOutput out) throws IOException { 
            this.tag.write(out);
            out.writeUTF(this.data.getClass().getName());  //使用序列化  需要传递类名
            this.data.write(out);
        }

        public void readFields(DataInput in) throws IOException {
            this.tag.readFields(in);
            String dataClz = in.readUTF();
            if (this.data == null
                    || !this.data.getClass().getName().equals(dataClz)) {
                try {
					this.data = (Writable) ReflectionUtils.newInstance(
					        Class.forName(dataClz), null);
				} catch (ClassNotFoundException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
            }
            this.data.readFields(in);
        }
    }
    
    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);
    }
}

下面仔细分析上述代码的实现过程:
首先解释TaggedMapOutput类型,该类是一个抽象类,充当数据连接操作过程中,传输数据类型的基类,这是Datajoin软件包指定的类型,用户必须继承该类。在上述数据连接数据流图中,map任务的输出是,Text类型的组键,TaggedMapOutput类型的值(该值中包含一个Text类似的tag和一个Writable类型的原始记录值)。
TaggedMapOutput 的源代码如下:
public abstract class TaggedMapOutput implements Writable {
  protected Text tag;

  public TaggedMapOutput() {
    this.tag = new Text("");
  }

  public Text getTag() {
    return tag;
  }

  public void setTag(Text tag) {
    this.tag = tag;
  }

  public abstract Writable getData();
  
  public TaggedMapOutput clone(JobConf job) {
    return (TaggedMapOutput) WritableUtils.clone(this, job);
  }

}
由于TaggedMapOutput类型需要在集群直接通过网络进行传递,所以实现了Writable接口。所以子类继承时候需要实现getData抽象方法和Writable接口中的方法。个人理解就是,继承该类, 就是将数据文件中的每条记录封装成一个个数据包。数据包包含一个tag和原始的记录值2个部分。

现在分析DataJoinMapperBase抽象类,在上述Datajoin.java代码中可以看到,子类实现了3个父类的抽象方法。
  • generateInputTag 方法给整个数据文件定义一个全局的tag,这里习惯将数据源的名称例如 customer作为全局的tag,该tag在后来会被set到每个记录对应的TaggedMapOutput 中,查看DataJoinMapperBase的源代码可以发现,该类使用inputTag变量保存全局的tag。 this.inputTag = generateInputTag(this.inputFile);
  • generateTaggedMapOutput方法就是将原始记录封装成数据包(TaggedMapOutput),该方法使用原始记录的值作为输入,返回封装好的数据包。
  • generateGroupKey方法使用TaggedMapOutput作为输入,从数据包中取得原始记录值,然后提取出组键值,用户在此次可以根据需要在记录原始值中选取组键值
需要提醒的是,DataJoinMapperBase抽象类已经实现了map方法,用户不用再去实现该方法了

分析完了DataJoinMapperBase,分析DataJoinReducerBase,DataJoinReducerBase是Datajoin软件包的核心,该抽象类只有一个抽象方法,需要用户实现,方法源码如下:
/**
   * 
   * @param tags
   *          a list of source tags
   * @param values
   *          a value per source
   * @return combined value derived from values of the sources
   */
  protected abstract TaggedMapOutput combine(Object[] tags, Object[] values);

  public void map(Object arg0, Object arg1, OutputCollector arg2,
                  Reporter arg3) throws IOException {
    // TODO Auto-generated method stub

  }
在combine方法中,我需要做如下工作,筛选不需要的组合,执行联接操作(外连接,内连接),将联接的结果进行合并以合适的格式进行输出。在两个数据源情况下,合并的记录数要么2个,要么1个。从combine方法的签名中可以看到,参数为一个标签数组和一个值数组。由于一个值对应一个标签,所以这两个数组的大小应该是一样的。以本为开始数据文件为输入,某次combine调用,tag数组和value数组内容如下:
tags = {customer,order}
values = {"1,aaaaaaa,4444444444444"  ,    "1,y,12.02,04-Mon-2008 "  }
在上述情况下,combine将2条来自不同tag的记录,合并为一条记录,方法见DataJoin.java中的相关代码。

参考: 《hadoop in Action》




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值