《Hadoop实战》之联结不同来源的数据

Reduce端的联结

reduce端联结,又称repartitioned join(重分区联结)或者reparationed sort-merge join(重分区排序-合并联结)

reduce侧联结的主要问题是,混洗阶段消耗过大。

先看几个术语与概念

  • data source:数据源(类似MySql中的table),一个数据源可以有多个文件(需具备相同的Schema)
  • tag:标签,携带数据源元数据(如文件名)
  • group key:组键(类似于Mysql中的join key,联结键)

原理

  1. Map:从不同的数据源载入数据,用组键和标签封装每个记录
  2. shuffle:组键被设置为联结键,按联结键分组、排序
  3. reduce:对每个组内的记录解包,得到原始数据,并进行完全交叉乘积(笛卡尔积),
  4. combine:根据内联结、外联结等方式, 将交叉乘积的结果合并到一条记录中

使用(API已被弃用)

Hadoop的datajoin包实现了联结的数据流,提供了3个可供继承和具体化的抽象类

  • DataJoinMapperBase
  • DataJoinReducerBase:执行完整的外部联结
  • TaggedMapOutput
TaggedMapOutput

datajoin软件包指定了组键为Text类型,而值则为TaggedMapOutput类型,TaggedMapOutput的作用是用Text标签封装记录,它

  • 提供了getTag()和setTag(Text tag)方法
  • 提供了getData()接口

例:自定义TaggedWritable

  • 实现getData()接口,data在构造函数传入,如下所示
// 该子类继承了TaggedMapOutput,可以处理所有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;
	}
	...
}
DataJoinMapperBase

提供了封装的接口,由DataJoinMapperBase的map调用

protected abstract Text generateInputTag(String inputFile);  // 任务开始时被调用,指定全局标签
protected abstract TaggedMapOutput generateTaggedMapOutput(Object value)	//
protected abstract Text generateGroupKey(TaggedMapOutput aRecord)

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);
}

例:实现接口

  • 给mapper设置全局标签:文件名/文件名前缀
// 文件名
protected Text generateInputTag(String inputFile) {
	return new Text(inputFile);	
}

// 文件名前缀
protected Text generateInputTag(String inputFile) {
	return new Text(inputFile.split('-')[0]);	
}
  • 用TaggedMapOutput封装记录,并用mapper的全局标签给记录设置标签
protected TaggedMapOutput generateTaggedMapOutput(Object value) {
	TaggedWritable retv = new TaggedWritable((Text) value);
	retv.setTag(this.inputTag);
	return retv;
}
  • 用Text封装组键,这里是第一个字段作为建
protected Text generateGroupKey(TaggedMapOutput aRecord) {
    String line = ((Text) aRecord.getData()).toString();
    String[] tokens = line.split(",");
   	String groupKey = tokens[0];
    return new Text(groupKey);
}
DataJoinReducerBase

combine()方法位于DataJoinReducerBase,用于筛选掉不需要的组合,并设置合适的输出格式

  • 输入:tags和values(reduce笛卡尔积结果)
  • 输出:TaggedMapOutput,联结后的值
  • combine只需要联结值,因此在combine内联结时需要把去掉键
protected abstract TaggedMapOutput combine(Object[] tags, Object[] values);

基于DistributedCache的复制联结(map端联结)

如果可以保证在联结一条记录是可以访问所有需要的数据,在map端联结即可实现

  • 当两个数据表为一大一小(可以装进mapper内存),则可使用该联结,称为复制联结(replicated join)

  • 结合Hadoop DistributedCache机制,可以将小表(背景数据)复制到所有节点上

    • DistractedCache.addCacheFile()
    • DistractedCache.getLocalCacheFiles()

例子:根据id联结customers和orders

  • customers.csv
1 Stephanie Leung 555-555-555
2 Edward Kim 123-456-7890
3 Jose Madriz 281-330-8004
4 David Stork 408-555-0000
  • orders.csv
1 A 12.95 2-Jun-08
2 B 88.25 ######
3 C 32 ######
4 D 25.02 ######
  • 如果小数据源存储在客户端的本地文件系统中,可以通过-files参数指定

    (由于我自己跑单机测试,小数据源在E盘中,因此稍微修改了下代码)

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.filecache.DistributedCache;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapred.*;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.net.URI;
import java.util.Hashtable;


public class MapJoinDemo extends Configured implements Tool {

    public static class MapClass extends MapReduceBase implements Mapper<Text, Text, Text, Text> {
        private Hashtable<String, String> joinData = new Hashtable<>();

        @Override
        public void configure(JobConf job) {
            try {
                Path[] cacheFiles = DistributedCache.getLocalCacheFiles(job);
                if (cacheFiles != null) {
                    String line;
                    String[] tokens;
                    String path = "E" + cacheFiles[0].toString().substring(4);  // 适应Windows单机任务的下下策
                    try (BufferedReader joinReader = new BufferedReader(new FileReader(path))) {
                        while ((line = joinReader.readLine()) != null) {
                            tokens = line.split(",", 2);
                            joinData.put(tokens[0], tokens[1]);
                        }
                    }
                }
            } catch (IOException e) {
                System.err.println("Exception reading DistributedCache: " + e);
            }
        }

        @Override
        public void map(Text key, Text value, OutputCollector<Text, Text> outputCollector, Reporter reporter) throws IOException {
            String joinValue = joinData.get(key.toString());
            if (joinValue != null) {
                outputCollector.collect(key, new Text(value.toString() + "," + joinValue));
            }
        }
    }

    @Override
    public int run(String[] args) throws Exception {
        JobConf job = new JobConf(getConf(), getClass());
        job.setJobName("MapJoinTest");
        FileInputFormat.addInputPath(job, new Path(args[0]));
        FileOutputFormat.setOutputPath(job, new Path(args[1]));

        job.setInputFormat(KeyValueTextInputFormat.class);
        job.setOutputFormat(TextOutputFormat.class);
        job.set("key.value.separator.in.input.line", ",");
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(Text.class);

        job.setMapperClass(MapClass.class);
        // 适应Windows单机任务的下下策
        DistributedCache.addCacheFile(new URI("file:///my_code/bigdata/learn/joinDemo/src/main/resources/customers.csv"), job);

        JobClient.runJob(job);

        return 0;
    }

    public static void main(String[] args) throws Exception {
        int exitCode = ToolRunner.run(new Configuration(), new MapJoinDemo(), args);
        System.exit(exitCode);
    }
}

半联结:map侧过滤后在reduce侧联结

若两个表的数据过大无法进行复制联结,但是如果只对某个键进行联结

  • 可以先筛选出数据作为临时文件,进行复制联结
  • mapper过滤不需要的数据,在reduce端进行连接
posted @ 2020-12-08 20:25  vlj  阅读(142)  评论(0编辑  收藏  举报