MR 二次排序

自定义实现MR 的二次排序

在一个数据文件中,首先按照key排序。
在key相同的情况下,按照value大小排序的情况称为二次排序。

  • 自定义key :NewKey实现比较规则
  • 自定义GroupingComparator方法

比较过程

Hadoop权威指南


map阶段:
开始产生输出时,并不是直接写在磁盘上,而是写在缓冲区里(默认大小100M),当达到0.8时后台进程溢写到磁盘,(这些都可配置)。在缓冲区溢写到磁盘的过程中会进行排序和分组,溢写的磁盘文件也是很多的小文件组成,在这些小文件中都是排序和分组后的结果。在reduce端到map端读取文件之前,这些小文件还要进行合并成一个大文件,合并成大文件的过程也进行了排序和分组。

reduce阶段:
读取多个map产生的结果文件到内存,按照相同的分区信息进行重组,按顺序对重组后的文件进行处理。结束后输出到磁盘文件。


combiner过程应该在map阶段产生大文件后进行
partitioner过程应该由MRAppMaster主导,map进行完后向MRAppMaster进行汇报,MRAppMaster通知各个
reducer到map产生的结果文件的具体位置读取数据

详细代码

NewKey

package com.wowSpark.secondarySort;

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

import org.apache.hadoop.io.WritableComparable;

public class NewKey implements WritableComparable<NewKey> {

    private int first;
    private int second;

    public int getFirst() {
        return first;
    }

    public int getSecond() {
        return second;
    }

    public void set(int first, int second) {
        this.first = first;
        this.second = second;
    }

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

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

    //对key排序时,调用这个compareTo方法
    @Override
    public int compareTo(NewKey o) {
        if (first != o.first) {
            return first - o.first;
        } else if (second != o.second) {
            return second - o.second;
        } else {
            return 0;
        }
    }

    //新定义的类应该重写下面两个方法 
    @Override
    public int hashCode() {
        return first+"".hashCode() + second+"".hashCode();
    }

    @Override
    public boolean equals(Object first) {
        if (first instanceof NewKey){
            NewKey r = (NewKey) first;
            return r.first == this.first && r.second == this.second;
        }else{
            return false;
        }
    }
}

GroupingComparator

package com.wowSpark.secondarySort;

import com.wowSpark.secondarySort.NewKey;

import org.apache.hadoop.io.RawComparator;
import org.apache.hadoop.io.WritableComparator;

public class GroupingComparator implements RawComparator<NewKey>{

    @Override
    public int compare(NewKey o1, NewKey o2) {
        int l = o1.getFirst();
        int r = o2.getFirst();
        return l == r ? 0 : (l < r ? 1 : 1);
    }

    //一个字节一个字节的比,直到找到一个不相同的字节时比较这个字节的大小作为两个字节流的大小比较结果。
    @Override
    public int compare(byte[] b1, int s1, int l1, byte[] b2, int s2, int l2) {
        return WritableComparator.compareBytes(b1, s1, Integer.SIZE/8, b2, s2, Integer.SIZE/8);
    }
    /**
     * 
    //第二种方法,继承WritableComparator
    public static class GroupingComparator extends WritableComparator
    {
        protected GroupingComparator()
        {
            super(NewKey.class, true);
        }
        @Override
        //Compare two WritableComparables.
        public int compare(WritableComparable w1, WritableComparable w2)
        {
            NewKey nk1 = (NewKey) w1;
            NewKey nk2 = (NewKey) w2;
            int l = nk1.getFirst();
            int r = nk2.getFirst();
            return l == r ? 0 : (l < r ? -1 : 1);
        }
    }    
     */ 
}

自定义Mapper类

package com.wowSpark.secondarySort;

import java.io.IOException;
import java.util.StringTokenizer;

import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;

public class MyMapper extends Mapper<LongWritable, Text, NewKey, IntWritable>{

    private NewKey key = new NewKey();
    private IntWritable value = new IntWritable();

    @Override
    protected void map(LongWritable inKey, Text inValue, Context context)
            throws IOException, InterruptedException {
        StringTokenizer tokenizer = new StringTokenizer(inValue.toString());
        int first = 0;
        int second = 0;
        if(tokenizer.hasMoreTokens()){
            first = Integer.parseInt(tokenizer.nextToken());
            if(tokenizer.hasMoreTokens()) second = Integer.parseInt(tokenizer.nextToken());
            key.set(first, second);
            value.set(second);;
            context.write(key, value);
        }
    }
}

自定义Reducer类

package com.wowSpark.secondarySort;

import java.io.IOException;

import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;

public class MyReducer extends Reducer<NewKey, IntWritable, Text, IntWritable>{

    private final Text first = new Text();
    private final Text SEPARATOR = new Text("^^^^^^^^^^^");

    @Override
    protected void reduce(NewKey newkey, Iterable<IntWritable> values, Context context)
            throws IOException, InterruptedException {
        context.write(SEPARATOR, null);
        first.set(Integer.toString(newkey.getFirst()));
        for(IntWritable val: values){
            context.write(first, val);
        }
    }

}

main方法

package com.wowSpark.secondarySort;

import java.io.IOException;
import java.net.URI;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
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;

public class Driver {

    public static void main(String[] args) throws Exception {
        //获取JOB对象
        //Configuration conf = new Configuration();
        Job job = Job.getInstance();
        job.setJarByClass(Driver.class);

        FileSystem.newInstance(new URI("hdfs://h1:9000"), new Configuration()).delete(new Path("hdfs://h1:9000/mr/out"), true);

        job.setMapperClass(MyMapper.class);
        job.setMapOutputKeyClass(NewKey.class);
        job.setMapOutputValueClass(IntWritable.class);

        job.setReducerClass(MyReducer.class);
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(Text.class);

        job.setGroupingComparatorClass(GroupingComparator.class);

        FileInputFormat.setInputPaths(job, "hdfs://h1:9000/mr/data.dat");
        FileOutputFormat.setOutputPath(job, new Path("hdfs://h1:9000/mr/out"));

        System.out.println(job.waitForCompletion(true)? 0 : 1);     
    }   
}


参考:

【1】 http://blog.youkuaiyun.com/wowSpark/article/details/49616083 < Exception>
【2】 http://download.youkuaiyun.com/detail/wowspark/9236975 <源码下载>

<think>好的,用户想比较和理解Spark Join与MapReduce Join的区别及用法。首先,我需要回顾一下这两个框架的基本原理,以及它们在处理数据连接时的不同方法。 首先,MapReduce是Hadoop的经典计算模型,基于分治思想,分为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),根据数据大小自动选择最优策略,而MapReduce的Join策略相对固定,需要手动优化。 使用场景方面,MapReduce适合超大规模数据且对延迟不敏感的场景,比如离线批处理。而Spark更适合需要低延迟和迭代计算的场景,比如机器学习或实时分析,尤其是在需要多次操作同一数据集时,Spark的内存优势明显。 此外,用户可能还想了解具体的优化方法,比如在Spark中如何选择Join类型,或者如何调整分区策略来优化性能。同时,他们可能对两种框架的API和易用性感兴趣,比如Spark的DataFrame API比MapReduce的Java代码更简洁。 需要检查引用内容是否有相关支持。引用3提到Spark的BroadcastHashJoinExec和如何通过ChildDistribution优化数据分布,这可以解释Spark的广播连接机制。引用4提到高级API如Hive优化执行计划,可能涉及Spark SQL的优化器。这些都可以作为回答的支撑。 总结,用户需要明确的对比点包括执行机制、性能、优化策略、适用场景以及易用性。需要结构清晰,分点说明,并引用相关技术细节来增强可信度。同时,生成的相关问题要引导用户进一步探索优化方法和实际应用案例。</think>### Spark Join与MapReduce Join的对比分析 #### 一、执行机制差异 1. **MapReduce Join** 采用分阶段Shuffle机制,需通过Map阶段输出键值对,Reduce阶段完成数据合并。例如处理表$A$和表$B$的Join时: - 两表数据按Join Key哈希分区 - 所有相同Key的数据通过网络传输到同一Reducer - 实现方式包括**Reduce端Join**(通用但效率低)和**Map端Join**(需小表预加到内存)[^3] ```python # MapReduce伪代码示例(Reduce端Join) 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、付费专栏及课程。

余额充值