Map Join和Reduce Join的区别

MapReduce Join

对两份数据data1和data2进行关键词连接是一个很通用的问题,如果数据量比较小,可以在内存中完成连接。

如果数据量比较大,在内存进行连接操会发生OOM。mapreduce join可以用来解决大数据的连接。
1 思路
1.1 reduce join

在map阶段, 把关键字作为key输出,并在value中标记出数据是来自data1还是data2。因为在shuffle阶段已经自然按key分组,reduce阶段,判断每一个value是来自data1还是data2,在内部分成2组,做集合的乘积。

这种方法有2个问题:

1, map阶段没有对数据瘦身,shuffle的网络传输和排序性能很低。

2, reduce端对2个集合做乘积计算,很耗内存,容易导致OOM。

1.2 map join

两份数据中,如果有一份数据比较小,小数据全部加载到内存,按关键字建立索引。大数据文件作为map的输入文件,对map()函数每一对输入,都能够方便地和已加载到内存的小数据进行连接。把连接结果按key输出,经过shuffle阶段,reduce端得到的就是已经按key分组的,并且连接好了的数据。

这种方法,要使用Hadoop中的DistributedCache把小数据分布到各个计算节点,每个map节点都要把小数据库加载到内存,按关键字建立索引。

这种方法有明显的局限性:有一份数据比较小,在map端,能够把它加载到内存,并进行join操作。

1.3 使用内存服务器,扩大节点的内存空间

针对map join,可以把一份数据存放到专门的内存服务器,在map()方法中,对每一个的输入对,根据key到内存服务器中取出数据,进行连接
1.4 使用BloomFilter过滤空连接的数据

对其中一份数据在内存中建立BloomFilter,另外一份数据在连接之前,用BloomFilter判断它的key是否存在,如果不存在,那这个记录是空连接,可以忽略。
1.5 使用mapreduce专为join设计的包

在mapreduce包里看到有专门为join设计的包,对这些包还没有学习,不知道怎么使用,只是在这里记录下来,作个提醒。

jar: mapreduce-client-core.jar

package: org.apache.hadoop.mapreduce.lib.join

2 实现map join

相对而言,map join更加普遍,下面的代码使用DistributedCache实现map join
2.1 背景

有客户数据customer和订单数据orders。

customer
客户编号 姓名 地址 电话
1 hanmeimei ShangHai 110
2 leilei BeiJing 112
3 lucy GuangZhou 119

* order*
订单编号 客户编号 其它字段被忽略
1 1 50
2 1 200
3 3 15
4 3 350
5 3 58
6 1 42
7 1 352
8 2 1135
9 2 400
10 2 2000
11 2 300

要求对customer和orders按照客户编号进行连接,结果要求对客户编号分组,对订单编号排序,对其它字段不作要求
客户编号 订单编号 订单金额 姓名 地址 电话
1 1 50 hanmeimei ShangHai 110
1 2 200 hanmeimei ShangHai 110
1 6 42 hanmeimei ShangHai 110
1 7 352 hanmeimei ShangHai 110
2 8 1135 leilei BeiJing 112
2 9 400 leilei BeiJing 112
2 10 2000 leilei BeiJing 112
2 11 300 leilei BeiJing 112
3 3 15 lucy GuangZhou 119
3 4 350 lucy GuangZhou 119
3 5 58 lucy GuangZhou 119

在提交job的时候,把小数据通过DistributedCache分发到各个节点。
map端使用DistributedCache读到数据,在内存中构建映射关系--如果使用专门的内存服务器,就把数据加载到内存服务器,map()节点可以只保留一份小缓存;如果使用BloomFilter来加速,在这里就可以构建;
map()函数中,对每一对,根据key到第2)步构建的映射里面中找出数据,进行连接,输出。

2.2 程序实现

public class Join extends Configured implements Tool {
// customer文件在hdfs上的位置。
// TODO: 改用参数传入
private static final String CUSTOMER_CACHE_URL = “hdfs://hadoop1:9000/user/hadoop/mapreduce/cache/customer.txt”;
private static class CustomerBean {
private int custId;
private String name;
private String address;
private String phone;

    public CustomerBean() {}

    public CustomerBean(int custId, String name, String address,
            String phone) {
        super();
        this.custId = custId;
        this.name = name;
        this.address = address;
        this.phone = phone;
    }



    public int getCustId() {
        return custId;
    }

    public String getName() {
        return name;
    }

    public String getAddress() {
        return address;
    }

    public String getPhone() {
        return phone;
    }
}

private static class CustOrderMapOutKey implements WritableComparable<CustOrderMapOutKey> {
    private int custId;
    private int orderId;

    public void set(int custId, int orderId) {
        this.custId = custId;
        this.orderId = orderId;
    }

    public int getCustId() {
        return custId;
    }

    public int getOrderId() {
        return orderId;
    }

    @Override
    public void write(DataOutput out) throws IOException {
        out.writeInt(custId);
        out.writeInt(orderId);
    }

    @Override
    public void readFields(DataInput in) throws IOException {
        custId = in.readInt();
        orderId = in.readInt();
    }

    @Override
    public int compareTo(CustOrderMapOutKey o) {
        int res = Integer.compare(custId, o.custId);
        return res == 0 ? Integer.compare(orderId, o.orderId) : res;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof CustOrderMapOutKey) {
            CustOrderMapOutKey o = (CustOrderMapOutKey)obj;
            return custId == o.custId && orderId == o.orderId;
        } else {
            return false;
        }
    }

    @Override
    public String toString() {
        return custId + "\t" + orderId;
    }
}

private static class JoinMapper extends Mapper<LongWritable, Text, CustOrderMapOutKey, Text> {
    private final CustOrderMapOutKey outputKey = new CustOrderMapOutKey();
    private final Text outputValue = new Text();

    /**
     * 在内存中customer数据
     */
    private static final Map<Integer, CustomerBean> CUSTOMER_MAP = new HashMap<Integer, Join.CustomerBean>();
    @Override
    protected void map(LongWritable key, Text value, Context context)
            throws IOException, InterruptedException {

        // 格式: 订单编号 客户编号    订单金额
        String[] cols = value.toString().split("\t");           
        if (cols.length < 3) {
            return;
        }

        int custId = Integer.parseInt(cols[1]);     // 取出客户编号
        CustomerBean customerBean = CUSTOMER_MAP.get(custId);

        if (customerBean == null) {         // 没有对应的customer信息可以连接
            return;
        }

        StringBuffer sb = new StringBuffer();
        sb.append(cols[2])
            .append("\t")
            .append(customerBean.getName())
            .append("\t")
            .append(customerBean.getAddress())
            .append("\t")
            .append(customerBean.getPhone());

        outputValue.set(sb.toString());
        outputKey.set(custId, Integer.parseInt(cols[0]));

        context.write(outputKey, outputValue);
    }

    @Override
    protected void setup(Context context)
            throws IOException, InterruptedException {
        FileSystem fs = FileSystem.get(URI.create(CUSTOMER_CACHE_URL), context.getConfiguration());
        FSDataInputStream fdis = fs.open(new Path(CUSTOMER_CACHE_URL));

        BufferedReader reader = new BufferedReader(new InputStreamReader(fdis));
        String line = null;
        String[] cols = null;

        // 格式:客户编号  姓名  地址  电话
        while ((line = reader.readLine()) != null) {
            cols = line.split("\t");
            if (cols.length < 4) {              // 数据格式不匹配,忽略
                continue;
            }

            CustomerBean bean = new CustomerBean(Integer.parseInt(cols[0]), cols[1], cols[2], cols[3]);
            CUSTOMER_MAP.put(bean.getCustId(), bean);
        }
    }
}

/**
 * reduce
 * @author Ivan
 *
 */
private static class JoinReducer extends Reducer<CustOrderMapOutKey, Text, CustOrderMapOutKey, Text> {
    @Override
    protected void reduce(CustOrderMapOutKey key, Iterable<Text> values, Context context)
            throws IOException, InterruptedException {
        // 什么事都不用做,直接输出
        for (Text value : values) {
            context.write(key, value);
        }
    }
}
/**
 * @param args
 * @throws Exception
 */
public static void main(String[] args) throws Exception {
    if (args.length < 2) {
        new IllegalArgumentException("Usage: <inpath> <outpath>");
        return;
    }

    ToolRunner.run(new Configuration(), new Join(), args);
}

@Override
public int run(String[] args) throws Exception {
    Configuration conf = getConf();
    Job job = Job.getInstance(conf, Join.class.getSimpleName());
    job.setJarByClass(SecondarySortMapReduce.class);

    // 添加customer cache文件
    job.addCacheFile(URI.create(CUSTOMER_CACHE_URL));

    FileInputFormat.addInputPath(job, new Path(args[0]));
    FileOutputFormat.setOutputPath(job, new Path(args[1]));

    // map settings
    job.setMapperClass(JoinMapper.class);
    job.setMapOutputKeyClass(CustOrderMapOutKey.class);
    job.setMapOutputValueClass(Text.class);

    // reduce settings
    job.setReducerClass(JoinReducer.class);
    job.setOutputKeyClass(CustOrderMapOutKey.class);
    job.setOutputKeyClass(Text.class);

    boolean res = job.waitForCompletion(true);

    return res ? 0 : 1;
}

}

运行环境

操作系统: Centos 6.4
Hadoop: Apache Hadoop-2.5.0

==客户数据文件在hdfs上的位置硬编码为==
hdfs://hadoop1:9000/user/hadoop/mapreduce/cache/customer.txt, 运行程序之前先把客户数据上传到这个位置。

程序运行结果
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: MapjoinReducejoin是两种用于在Hadoop MapReduce中进行数据连接的方法。 Mapjoin是将小表加载到内存中,在map阶段进行连接,不需要在reduce阶段进行连接。这种方法适用于小表和大表的连接。 Reducejoin则是在reduce阶段进行连接,适用于两个大表的连接。 ### 回答2: MapReduce是一种分布式计算模型,用于在大规模数据集上进行并行计算。在MapReduce模型中,MapReduce是两个重要的阶段,其中Map阶段将数据分片并进行预处理,Reduce阶段将Map阶段的输出结果进行合并,并最终输出计算结果。 在MapReduce中,MapJoinReduceJoin是两个比较常见的数据处理方式。MapJoin是指在Map阶段中使用数据缓存或索引的方法将两个或多个数据集进行连接操作。而ReduceJoin则是在Reduce阶段中将两个或多个数据集进行连接操作。 MapJoinReduceJoin区别在于它们的适用场景和性能表现。MapJoin通常用于一对一或者多对一的数据连接操作,适用于大数据集和小数据集的快速连接。由于MapJoinMap阶段中进行连接操作,因此可以利用数据缓存或索引的优势,避免在Reduce阶段中进行大量的数据读写操作,从而提高了数据处理的效率和性能。 而ReduceJoin则适用于大数据集和大数据集之间的连接操作。由于Reduce阶段中涉及到大量的数据读写操作,因此需要在集群中进行大量的数据传输和复制,这会造成大量的网络和IO开销,从而导致性能下降。同时,ReduceJoin还需要进行数据的排序和分组操作,这会对集群的计算能力和内存压力造成很大的负担。 因此,MapJoinReduceJoin需要根据实际的数据规模和连接需求进行选择,以最优的方式进行数据处理和计算。在实际的MapReduce应用中,可以根据不同的业务特点和数据分布情况,选择适合自己的连接方式,以提高数据处理的效率和性能。 ### 回答3: MapJoinReduceJoin是Hadoop中两种常见的连接机制。MapJoin通常用于小数据集之间的连接,而ReduceJoin则适用于大数据集之间的连接。下面具体说明两者的区别: 1. MapJoin MapJoin是通过将两个表的数据都读入内存中,在Map端将这些数据进行连接,然后返回给Reduce端。在MapJoin中,一个表的数据作为Map输入,另一个表则存储在内存中作为Hash表,Map会对所有的数据进行扫描和匹配,最后将结果输出到Reduce端。由于MapJoin需要将所有数据全放在内存中,因此常用于小数据量的表之间的连接关系。 2. ReduceJoin ReduceJoin则是将两个表的数据集分别发送到Map端,Map端会对这两个数据集进行排序,然后对相同的Key进行匹配和连接,最后将结果返回给Reduce端。在ReduceJoin中,两个输入的数据集会被排序,相同的Key会被发送到同一个Reducer中进行操作,并将结果输出到Reduce端。ReduceJoin常用于大数据集之间的连接关系,因为它可以避免内存溢出的问题,但是需要进行排序和数据再次传输等过程,因此效率相对MapJoin会低一些。 综上所述,MapJoinReduceJoin都是链接两个数据集的机制,但是它们有着不同的使用场景。MapJoin适用于小数据集之间的连接,能够提供高效的连接性能;而ReduceJoin则适用于大数据集之间的连接,能够避免内存不足的问题,但是需要付出排序和数据传输等成本。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值