MR之ReduceJoin


前言

前一篇文章将了下MapJoin,其实ReduceJoin和MapJoin类似,只不过一个是在Map端匹配,一个是在Reduce端匹配,各有各的优势。

一、实现思路

和MapJoin类似,也是通过读取二个文件,文件的大小可以很大,通过FileInputFormat读取文件,读取到文件后需要获取文件的名称,通过文件名称来区分对应的是订单还是产品文件,再封装成对象输出,输出的key值为二个文件公有的产品id,从而到reduce端就可以获取到二个文件的数据,最后再进行匹配名称即可。

二、具体代码

1.OrderBean类

package com.hadoop.mapreduce.reduceJoin;

import org.apache.hadoop.io.Writable;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;

/**
 * @author LengQing
 * @date 2020/5/3 - 15:20
 * 实现将订单表和产品表数据合并
 */
public class OrderBean implements Writable {
    private String o_id; // order表:订单id
    private String p_id; // order表、product表:商品id
    private double o_price; // order表:商品单价
    private Integer o_num; // order表:商品数量
    private String p_name; // product表:商品名称
    private String flag; // 表标识

    public OrderBean() {
        super();
    }

    public OrderBean(String o_id, String p_id, double o_price, Integer o_num, String p_name, String flag) {
        this.o_id = o_id;
        this.p_id = p_id;
        this.o_price = o_price;
        this.o_num = o_num;
        this.p_name = p_name;
        this.flag = flag;
    }
    @Override
    public String toString() {
        return o_id + '\t' + p_name + '\t' + o_num + '\t' + o_price;
    }

    public String getO_id() {
        return o_id;
    }

    public void setO_id(String o_id) {
        this.o_id = o_id;
    }

    public String getP_id() {
        return p_id;
    }

    public void setP_id(String p_id) {
        this.p_id = p_id;
    }

    public double getO_price() {
        return o_price;
    }

    public void setO_price(double o_price) {
        this.o_price = o_price;
    }

    public Integer getO_num() {
        return o_num;
    }

    public void setO_num(Integer o_num) {
        this.o_num = o_num;
    }

    public String getP_name() {
        return p_name;
    }

    public void setP_name(String p_name) {
        this.p_name = p_name;
    }

    public String getFlag() {
        return flag;
    }

    public void setFlag(String flag) {
        this.flag = flag;
    }

    @Override
    public void write(DataOutput out) throws IOException {
        out.writeUTF(o_id);
        out.writeUTF(p_id);
        out.writeDouble(o_price);
        out.writeInt(o_num);
        out.writeUTF(p_name);
        out.writeUTF(flag);
    }

    @Override
    public void readFields(DataInput in) throws IOException {
        o_id = in.readUTF();
        p_id = in.readUTF();
        o_price = in.readDouble();
        o_num = in .readInt();
        p_name = in.readUTF();
        flag = in.readUTF();
    }
}

2.ReduceJoinMapper类

package com.hadoop.mapreduce.reduceJoin;

import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.lib.input.FileSplit;
import java.io.IOException;

/**
 * @author LengQing
 * @date 2020/5/3 - 15:07
 */
public class ReduceJoinMapper extends Mapper<LongWritable, Text, Text, OrderBean> {
    private Text outputKey = new Text();
    private OrderBean orderBean = new OrderBean();
    private String name;


    @Override
    protected void setup(Context context) throws IOException, InterruptedException {
        // 1 获取输入文件切片
        FileSplit fileSplit = (FileSplit)context.getInputSplit();
        // 2 获取输入的文件名称
        name = fileSplit.getPath().getName();
    }

    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        String[] line = value.toString().split("\t");
        if (name.startsWith("order")){ // 封装order表数据对象
            this.outputKey.set(line[1]);
            orderBean.setO_id(line[0]);
            orderBean.setP_id(line[1]);
            orderBean.setO_num(Integer.parseInt(line[2]));
            orderBean.setO_price(Double.parseDouble(line[3]));
            orderBean.setP_name("");
            orderBean.setFlag("order");
        }else{ // 封装product表数据对象
            this.outputKey.set(line[0]);
            orderBean.setO_id("");
            orderBean.setP_id(line[0]);
            orderBean.setO_price(0);
            orderBean.setO_num(0);
            orderBean.setP_name(line[1]);
            orderBean.setFlag("product");
        }
        context.write(this.outputKey, orderBean);
    }
}

3.ReduceJoinReduce类

package com.hadoop.mapreduce.reduceJoin;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
import java.util.ArrayList;

/**
 * @author LengQing
 * @date 2020/5/3 - 15:08
 */
public class ReduceJoinReduce extends Reducer<Text, OrderBean, Text, NullWritable> {
    private Text outputKey = new Text();
    @Override
    protected void reduce(Text key, Iterable<OrderBean> values, Context context) throws IOException, InterruptedException {
        // 1 pd表对象,存放pd表数据
        OrderBean pdBean = new OrderBean();
        int num = 0;
        double price = 0.0;
        for (OrderBean value : values) {
            if ("order".equals(value.getFlag())){
                num += value.getO_num();
                price += value.getO_price();
            }else{
                try {
                    // 拷贝传递过来的产品表到内存中
                    BeanUtils.copyProperties(pdBean, value);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        outputKey.set(pdBean.getP_name() + "\t" + num + "\t" + price);
        context.write(outputKey, NullWritable.get());
    }
}

4.RecudeJoinDriver类

package com.hadoop.mapreduce.reduceJoin;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;

/**
 * @author LengQing
 * @date 2020/5/3 - 14:29
 * 需求:将order表中的按照产品编号汇总产品金额,并将产品编号替换成产品表的产品名称
 */
public class RecudeJoinDriver extends Configured implements Tool {
    private Configuration conf = new Configuration();
    @Override
    public int run(String[] args) throws Exception {
        // 实例化Job
        Job job = Job.getInstance(conf, "reduceJoin");
        job.setJarByClass(RecudeJoinDriver.class);

        // 1 input阶段
        Path inputPath = new Path(args[0]);
        FileInputFormat.setInputPaths(job, inputPath);

        // 2 map阶段
        job.setMapperClass(ReduceJoinMapper.class);
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(OrderBean.class);

        // 3 shuffle阶段

        // 4 reduce阶段
        job.setReducerClass(ReduceJoinReduce.class);
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(NullWritable.class);

        // 5 output阶段
        Path outputPath = new Path(args[1]);
        FileOutputFormat.setOutputPath(job, outputPath);

        return job.waitForCompletion(true) ? 0 : 1;
    }

    public static void main(String[] args) {
        try {
            int status = ToolRunner.run(new RecudeJoinDriver(), args);
            System.exit(status);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
<think>好的,用户想比较和理解Spark JoinMapReduce Join的区别及用法。首先,我需要回顾一下这两个框架的基本原理,以及它们在处理数据连接时的不同方法。 首先,MapReduceHadoop的经典计算模型,基于分治思想,分为Map和Reduce两个阶段。而Spark是基于内存计算的分布式框架,引入了弹性分布式数据集(RDD)和更丰富的操作符。两者的执行机制不同,这会影响Join操作的实现方式。 接下来,执行流程的区别。MapReduce Join通常需要在Reduce阶段进行数据合并,需要将相同键的数据通过网络传输到同一个Reducer,这可能导致较大的网络开销。而Spark的Join操作可以利用内存计算和更灵活的分区策略,可能减少数据移动,提高效率。 然后,数据分发的机制。MapReduce依赖Shuffle过程,所有相同键的数据必须传输到同一个节点,这在处理大规模数据时可能成为瓶颈。Spark的优化策略如Broadcast Join可以将小表广播到各个节点,避免大规模的Shuffle,这对于大小表连接特别有效。 性能方面,Spark的内存计算和DAG调度器能优化任务执行顺序,减少磁盘I/O,而MapReduce每一步都需要写磁盘,速度较慢。另外,Spark支持多种Join策略(如SortMergeJoin、BroadcastHashJoin),根据数据大小自动选择最优策略,而MapReduceJoin策略相对固定,需要手动优化。 使用场景方面,MapReduce适合超大规模数据且对延迟不敏感的场景,比如离线批处理。而Spark更适合需要低延迟和迭代计算的场景,比如机器学习或实时分析,尤其是在需要多次操作同一数据集时,Spark的内存优势明显。 此外,用户可能还想了解具体的优化方法,比如在Spark中如何选择Join类型,或者如何调整分区策略来优化性能。同时,他们可能对两种框架的API和易用性感兴趣,比如Spark的DataFrame API比MapReduce的Java代码更简洁。 需要检查引用内容是否有相关支持。引用3提到Spark的BroadcastHashJoinExec和如何通过ChildDistribution优化数据分布,这可以解释Spark的广播连接机制。引用4提到高级API如Hive优化执行计划,可能涉及Spark SQL的优化器。这些都可以作为回答的支撑。 总结,用户需要明确的对比点包括执行机制、性能、优化策略、适用场景以及易用性。需要结构清晰,分点说明,并引用相关技术细节来增强可信度。同时,生成的相关问题要引导用户进一步探索优化方法和实际应用案例。</think>### Spark JoinMapReduce Join的对比分析 #### 一、执行机制差异 1. **MapReduce Join** 采用分阶段Shuffle机制,需通过Map阶段输出键值对,Reduce阶段完成数据合并。例如处理表$A$和表$B$的Join时: - 两表数据按Join Key哈希分区 - 所有相同Key的数据通过网络传输到同一Reducer - 实现方式包括**ReduceJoin**(通用但效率低)和**Map端Join**(需小表预加载到内存)[^3] ```python # MapReduce伪代码示例(ReduceJoin) def map(key, value): emit(join_key, (table_tag, value)) def reduce(key, values): for (tag, data) in values: if tag == 'A': a_list.append(data) else: b_list.append(data) for a in a_list: for b in b_list: emit(key, (a,b)) ``` 2. **Spark Join** 基于内存计算和DAG优化,支持多种Join策略: - **Broadcast Hash Join**:小表广播到所有Executor,避免Shuffle(适用于$小表 \bowtie 大表$)[^3] - **Sort Merge Join**:大表间Join时先分区排序再合并(类似MapReduce但内存优化) - 自动选择策略(通过`spark.sql.autoBroadcastJoinThreshold`配置) #### 二、性能关键指标对比 | 维度 | MapReduce Join | Spark Join | |---------------------|---------------------------------|--------------------------------| | **数据移动** | 强制全量Shuffle | 可避免Shuffle(如Broadcast) | | **磁盘I/O** | 每阶段写磁盘 | 内存优先,减少磁盘交互 | | **延迟** | 分钟级~小时级 | 秒级~分钟级 | | **适用数据量** | 超大规模数据(PB级) | 中小规模数据(TB级以下更优) | #### 三、典型应用场景 1. **MapReduce Join适用场景** - 离线批处理(如每日全量用户日志分析) - 数据规模极大且硬件资源有限 - 需与Hadoop生态系统深度集成(如Hive on MapReduce) 2. **Spark Join优势场景** - 迭代计算(如机器学习特征关联) - 实时性要求较高的ETL流水线 - 多数据源混合处理(通过Spark SQL统一操作) - 需要复杂Join条件(如`df1.join(df2, expr("a.id = b.id AND a.ts > b.ts"))`) #### 四、优化实践对比 - **MapReduce优化** - 使用Combiner减少数据传输量 - 手动实现布隆过滤器过滤无效Key[^3] - **Spark优化** - 调整`spark.sql.shuffle.partitions`控制并行度 - 利用`cache()`缓存复用数据集 - 通过AQE(Adaptive Query Execution)动态优化Join策略[^4] #### 五、代码复杂度对比 ```python # Spark实现Join(Python API) df_result = df1.join(df2, "join_key", "inner") # MapReduce实现同等功能需约200行Java代码 ``` Spark通过高阶API将复杂性隐藏在运行时优化中[^4],而MapReduce需手动控制数据流。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值