4)Hadoop之MapReduce(MapReduce工作流程(shuffle、分区、排序))

MapReduce详细工作流程一:
  • 如图
MapReduce详细工作流程二:
  • 如图
Shuffle机制
  • Map方法之后,Reduce方法之前的数据处理过程称之为Shuffle。如下图所示:

  • 图解:

    1. MapTask搜集map()方法的kv对,放入内存缓冲区中
    2. 从内存不断溢写到本地磁盘文件,可能会溢出多个文件
    3. 多个溢出文件会被合并成大的溢出文件
    4. 在溢写过程和合并过程中,都要调用Partitioner进行分区和针对key进行排序
    5. ReduceTask根据自己的分区号取各个MapTask机器上取相应的的结果来分区数据
    6. ReduceTask会取到不同MapTask机器上同一分区的结果文件,再进行合并(归并排序)
    7. 合并成大文件后,shuffle阶段结束,后面进入ReduceTask的逻辑运算过程(从文件中取出一个一 个的键值对Group,调用用户自定义的reduce()方法)
    • 注意
      Shuffle中的缓冲区大小会影响到MapReduce程序的执行效率,原则上说,缓冲区越大,磁盘io的次数越少,执行速度就越快。(缓冲区的大小可以通过参数调整,参数:io.sort.mb默认100M)
Partition分区:
  1. 问题引出:
    要求将统计结果按照条件输出到不同文件;例如将统计结果按手机号码所属省份输出到不同的文件中(分区)
    1. 默认的Partitioner分区:
      public class HashPartitioner<K,V> extends Partitioner<K,V>{
          public int getPartitoion(K key,V value,int numReduceTask){
      	return (key.hashCode() & Integer.Integer.MAX_VALUE) % numReduceTasks;
      	//用户无法控制哪个key存储到哪个分区
          }
      }
    2. 自定义Partitioner步骤:
      ①自定义类继承Partitioner,重写getPartition()方法
      public class CustomPartitioner extends Partitioner<Text,FlowBean>{
      	@override
      	public int getPartitoion(K key,V value,int numReduceTask){
      		//控制分区的逻辑代码
      		
      		return partition;
      	}
      }
      ②在job驱动中,设置自定义Partitioner:
      Job.setPartitionerClass(CustomPartitioner.class)
      ③自定义partition后,要根据自定义的Partitioner的逻辑设置相应数量的ReduceTask:
      Job.setNumReduceTask(5);
    3. 分区总结:
      ①如果ReduceTask的数量 > getPartition的结果数:则会多产生几个输出文件part-r-000xx
      ②若 1 < ReduceTask的数量 < getPartition的结果数:有一部分数据无处安放,会报错
      ③若 ReduceTask = 1:则所有分区的结果都交给这一个ReduceTask,最终也只有一个输出文件part-r-00000
      ③分区号必须从零开始,逐一累加
    4. 案例分析:
      假设自定义分区数为5,则:
      job.setNumReduceTask(1);//正常运行,产生一个文件
      job.setNumReduceTask(2);//报错
      job.setNumReduceTask(6);//多产生一个空文件
  • 分区案例:
    1. 需求:将统计结果按照手机归属地不同省份输出到不同文件中(分区)
      ①输入数据格式:

      ②期望输出数据:

    2. 在原来序列化案例(求流量总和)基础上增加一个分区类:

public class ProvincePartitioner extends Partitioner<Text, FlowBean> {

   @Override
   public int getPartition(Text key, FlowBean value, int numPartitions) {
       //1 获取电话号码的前三位
      String preNum = key.toString().substring(0, 3);     
      int partition = 4;   
    
      //2 判断是哪个省
      if("136".equals(preNum)) {
          partition = 0;
      }else if ("137".equals(preNum)) {
          partition = 1;
      }else if ("138".equals(preNum)) {
          partition = 2;
      }else if ("139".equals(preNum)) {
          partition = 3;
      } 
      return partition;
   }
}
  1. 在驱动函数中增加自定义数据分区设置和ReduceTask设置:
public class FlowCountDriver {

    public static void main(String[] args) throws Exception {

        String s1 = "E:\\input\\input4";
        String s2 = "E:\\output\\output3";

        //获取配置信息,job对象
        Configuration con = new Configuration();
        Job job = Job.getInstance(con);

        //指定jar加载路径
        job.setJarByClass(FlowCountDriver.class);

        //设置map、reduce类
        job.setMapperClass(FlowCountMapper.class);
        job.setReducerClass(FlowCountReducer.class);

        //设置map输出
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(FlowBean.class);

        //设置最终的KV类型
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(FlowBean.class);

        //指定自定义数据分区
        job.setPartitionerClass(ProvincePartitioner.class);
        //指定相应的 reduceTask个数
        job.setNumReduceTasks(5);

        //指定job的输入原始路径和最终输出路径
        FileInputFormat.setInputPaths(job, new Path(s1));
        FileOutputFormat.setOutputPath(job, new Path(s2));

        //提交
        boolean b = job.waitForCompletion(true);

        System.exit(b ? 0 : 1);

    }

}
WritableComparable排序
  • 概述:
    • 排序是MapReduce框架最重要的操作之一。
    • MapTask和ReduceTask均会对key进行排序,这是Hadoop的默认行为,任何程序中的数据都会被排序,不管逻辑上是否需要
    • 默认排序是按照字典顺序排序,且实现该排序方法的是快速排序
    • 对于MapTask,它先将处理好的结果暂时放入环形缓冲区,当缓冲区的使用率达到一定的阈值时,再对缓冲区的数据进行一次快速排序,并将排好序的数据溢写到磁盘,当所有数据都处理完毕之后,它再对磁盘中的所有文件归并排序
    • 对于ReduceTask,它从每个MapTask远程拷贝相应的数据文件,若文件的大小超过一定阈值,则溢写到磁盘,否则存储在内存中;若磁盘中的文件数目达到一定阈值,则对文件进行归并排序生成一个更大的文件;若内存中的数据大小或数量超过一定阈值,则进行一次排序后溢写到磁盘,当所有文件拷贝完成,ReduceTask同一对内存和磁盘中所有数据进行一次归并排序
  1. 排序分类
    ①部分排序:
    MapReduce根据输入记录的键值对集排序,保证每个输出文件内部有序
    ②全排序:
    最终输出结果只有一个文件,且文件内部有序。实现方式是只设置一个ReduceTask,这种排序在处理大文件时效率极低,因为一台机器处理所有文件,完全丧失了MapReduce所提供的并行架构
    ③辅助排序(Grouping Comparable分组):
    在reduce端对key进行分组。应用于:在接收的key为bean对象时,想让一个或几个字段相同(全部字段比较不相同)的key进入到同一个reduce方法,可以采用分组排序。
    ④二次排序:
    在自定义排序过程中,如果compareTo中的判断条件为两个即为二次排序
  2. 自定义排序WritableComparable
    原理分析:当自定义bean对象当做key进行传输时,该类实现WritableComparator重写compareTo方法即可实现排序:
@Override
public int compareTo(FlowBean o) {
   
   int result;	 
   // 按照总流量大小,倒序排列
   if (sumFlow > bean.getSumFlow()) 
        result = -1;
   else if (sumFlow < bean.getSumFlow()) 
        result = 1;
   else 
        result = 0;    
        
    return result;
}
  • **WritableComparable排序案例实操之 全排序 **
    • 需求分析(根据总流量进行倒序排序)
      ①输入数据格式:
      ②输出数据格式:
      ③FlowBean实现WritableCompareable接口重写compareTo方法
      ④map类:
      context.write(bean,phoneNum)
      ⑤reduce类:

      //循环输出,避免总流量相同的情况
      for (Text text : values)
        context.write(text,key)
    • 代码实现

FlowBean类

import org.apache.hadoop.io.Writable;
import org.apache.hadoop.io.WritableComparable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;

public class FlowBean implements WritableComparable<FlowBean>, Writable {
    private int upFlow;
    private int downFlow;
    private int sumFlow;

    public FlowBean() {
    }

    public FlowBean(int upFlow, int downFlow) {
        this.upFlow = upFlow;
        this.downFlow = downFlow;
        this.sumFlow = upFlow + downFlow;
    }

    public int getUpFlow() {
        return upFlow;
    }

    public void setUpFlow(int upFlow) {
        this.upFlow = upFlow;
    }

    public int getDownFlow() {
        return downFlow;
    }

    public void setDownFlow(int downFlow) {
        this.downFlow = downFlow;
    }

    public int getSumFlow() {
        return sumFlow;
    }

    public void setSumFlow(int sumFlow) {
        this.sumFlow = sumFlow;
    }

    @Override
    public String toString() {
        return upFlow + "\t" + downFlow + "\t" + sumFlow;
    }
    
    @Override
    public int compareTo(FlowBean bean) {

        if (sumFlow > bean.getSumFlow())
            return -1;
        else if (sumFlow < bean.getSumFlow())
            return 1;
        else
            return 0;
    }

    @Override
    public void write(DataOutput dataOutput) throws IOException {
        dataOutput.writeInt(upFlow);
        dataOutput.writeInt(downFlow);
        dataOutput.writeInt(sumFlow);
    }

    @Override
    public void readFields(DataInput dataInput) throws IOException {
        this.upFlow = dataInput.readInt();
        this.downFlow = dataInput.readInt();
        this.sumFlow = dataInput.readInt();
    }
}

FlowCountSortMapepr类

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

public class FlowCountSortMapepr extends Mapper<LongWritable, Text, FlowBean, Text> {

    FlowBean k = new FlowBean();
    Text v = new Text();
    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {

        String line = value.toString();
        String[] flow = line.split("\t");

        String phoneNum = flow[1];
        int downFlow = Integer.parseInt(flow[flow.length - 2]);
        int upFlow = Integer.parseInt(flow[flow.length - 3]);

        k.setDownFlow(downFlow);
        k.setUpFlow(upFlow);
        k.setSumFlow(upFlow + downFlow);
        v.set(phoneNum);

        context.write(k, v);
    }
}

FlowCountSortReducer类



import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;

public class FlowCountSortReducer extends Reducer<FlowBean, Text, Text, FlowBean> {
    @Override
    protected void reduce(FlowBean v, Iterable<Text> values, Context context) throws IOException, InterruptedException {
        for (Text k : values)
            context.write(k, v);
    }
}

FlowCountSortDriver类

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
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 FlowCountSortDriver {
    public static void main(String[] args) throws Exception {
        String inputPath = "E:\\input\\input4";
        String outputPath = "E:\\output\\output1";

        //1 获取配置信息,job对象
        Configuration conf = new Configuration();
        Job job = Job.getInstance(conf);

        //2 指定 jar加载路径、map类、reduce类
        job.setJarByClass(FlowCountSortDriver.class);
        job.setMapperClass(FlowCountSortMapepr.class);
        job.setReducerClass(FlowCountSortReducer.class);

        //3 设置map的输入输出类型和最终的输入输出类型
        job.setMapOutputKeyClass(FlowBean.class);
        job.setMapOutputValueClass(Text.class);
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(FlowBean.class);

        //4 指定job的原始输入路径和最终输出路径
        FileInputFormat.setInputPaths(job, new Path(inputPath));
        FileOutputFormat.setOutputPath(job, new Path(outputPath));

        //5 提交任务
        boolean res = job.waitForCompletion(true);
        System.exit(res ? 0 : 1);
    }
}
  • WritableComparable排序案例实操之 区内排序
    需求:要求每个省份手机号输出的文件中按照总流量内部排序。
    输入格式:
    输出格式:

    案例实操:
    ①增加自定义分区类:
public class ProvincePartitioner extends Partitioner<FlowBean, Text> {
    @Override
    public int getPartition(FlowBean key, Text value, int numPartitions) {

        // 1 获取手机号码前三位
        String preNum = value.toString().substring(0, 3);
       
        int partition = 4;

        // 2 根据手机号归属地设置分区
        if ("136".equals(preNum)) {
            partition = 0;
        }else if ("137".equals(preNum)) {
            partition = 1;
        }else if ("138".equals(preNum)) {
            partition = 2;
        }else if ("139".equals(preNum)) {
            partition = 3;
        }
        return partition;
    }
}

②在驱动类中添加分区类:

// 加载自定义分区类
job.setPartitionerClass(ProvincePartitioner.class);
// 设置Reducetask个数
job.setNumReduceTasks(5);
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MapReduce中,排序是非常重要的。MapReduce在Map和Reduce的两个阶段中都会执行排序操作。全局排序是指在一个MapReduce程序产生的输出文件中,所有的结果都是按照某个策略进行排序的,例如降序还是升序。在全局排序中,只有一个reduce任务可以保证数据的全局有序,但这样无法充分利用Hadoop集群的优势。 在MapReduceshuffle过程中,通常会执行多次排序。首先是在Map输出阶段,根据分和key进行快速排序。然后,在Map的合并溢写文件阶段,将同一个分的多个溢写文件进行归并排序,合成一个大的溢写文件。最后,在Reduce输入阶段,将同一分来自不同Map任务的数据文件进行归并排序。最后阶段使用了堆排作为最后的合并过程。 在MapReduce中,有两种排序方式,即快速排序和归并排序。快速排序是通过一趟排序将要排序的数据分割成独立的两部分,然后对这两部分数据分别进行快速排序,最终达到整个数据变成有序序列的目的。归并排序是建立在归并操作上的一种排序算法,通过将已有序的子序列合并,得到完全有序的序列。归并排序可以采用分治法的方式进行,将子序列逐步合并,最终得到整个序列的有序结果。 因此,MapReduce中的排序操作是通过多次排序和归并的方式来实现的,以确保数据的有序性。<span class="em">1</span><span class="em">2</span><span class="em">3</span><span class="em">4</span>

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值