Hadoop框架---MapReduce框架原理(中)

一.MapReduce框架原理(中)

1.1 Shuffle机制

1.1.1 Shuffle机制

Map 方法之后,Reduce 方法之前的数据处理过程称之为 Shuffle
请添加图片描述

1.1.2 Partition分区

1、问题引出

要求将统计结果按照条件输出到不同文件中(分区)。比如:将统计结果按照手机归属地不同省份输出到不同文件中(分区)。

2.默认Partition分区

public class HashPartitioner<K, V> extends Partitioner<K, V> {

    public int getPartition(K key, V value, int numReduceTasks) {
        return (key.hashCode() & Integer.MAX_VALUE) % numReduceTasks;
    }
    
}

由源码可以看出:默认分区根据key的hashCode对ReduceTasks个数取模得到的。用户没法控制哪个key存储到哪个分区。

没有重写getPartition方法时,由于numReduceTasks的数量是设定好的,所以每一个key存放在哪个文件中取决于key对应的hashcode值!

3、自定义Partitioner步骤

(1)自定义类继承Partitioner,重写getPartition()方法

请添加图片描述
(2)在Job驱动中,设置自定义Partitioner

请添加图片描述

(3)自定义Partition后,要根据自定义Partitioner的逻辑设置相应数量的ReduceTask

请添加图片描述

1.1.3 Partition分区案例实操

1)需求

将统计结果按照手机归属地不同省份输出到不同文件中(分区)

(1)输入数据

请添加图片描述

(2)期望输出数据
手机号 136、137、138、139 开头都分别放到一个独立的 4 个文件中,其他开头的放到一个文件中

2)需求分析

请添加图片描述

3)在案例 Hadoop框架—Hadoop序列化中已有的Mapper,Reducer,Driver类的基础上,增加一个分区类

package com.root.mapreduce.patitioner2;

import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Partitioner;

public class ProvincePartitioner extends Partitioner<Text,FlowBean> {

    @Override
    public int getPartition(Text key, FlowBean value, int i) {
        //取手机号的前三位
        String s = key.toString();
        String result = s.substring(0, 3);
        
        //判断并返回分区号
        switch (result){
            case "136":return 0;
            case "137":return 1;
            case "138":return 2;
            case "139":return 3;
            default:return 4;
        }
    }
}

注:分区号必须从0开始,逐一累加!!!

4)在驱动函数中增加自定义数据分区设置和 ReduceTask 设置

package com.root.mapreduce.patitioner2;

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;

import java.io.IOException;

public class FlowDriver {
    public static void main(String[] args) throws IOException, InterruptedException, ClassNotFoundException {
        //1.获取job
        Configuration conf = new Configuration();
        Job ins = Job.getInstance(conf);

        //2.设置jar包路径
        ins.setJarByClass(FlowDriver.class);

        //3.关联mapper和reducer
        ins.setMapperClass(FlowMapper.class);
        ins.setReducerClass(FlowReducer.class);

        //4.设置map输出的kv类型
        ins.setMapOutputKeyClass(Text.class);
        ins.setMapOutputValueClass(FlowBean.class);

        //5.设置最终输出的kv类型
        ins.setOutputKeyClass(Text.class);
        ins.setOutputValueClass(FlowBean.class);

        //5+:设置自定义partitioner
        ins.setPartitionerClass(ProvincePartitioner.class);

        //5++:设置相应数量ReduceTask
        ins.setNumReduceTasks(5);

        //6.设置输入路径和输出路径
        FileInputFormat.setInputPaths(ins, new Path("D:\\java_learning\\input\\inputflow"));
        FileOutputFormat.setOutputPath(ins, new Path("D:\\java_learning\\output\\outputflow1"));

        //7.提交job
        boolean result = ins.waitForCompletion(true);

        System.exit(result ? 0 : 1);

    }
}

如果设置的ReduceTask数和设定的分区数不一致,则会分别出现以下状况:

本例中我们设置的分区为从0~4,共5个分区

(1)当NumReduceTasks>Partitions,也就是ins.setNumReduceTasks(n);中的参数n>5时,比如我们设置为6,那么run后的结果会产生冗余的空文件:

请添加图片描述

(2)当1<NumReduceTasks<Partitions时,报IO异常,因为一部分数据不知道该放置在哪:

请添加图片描述

(3)当NumReduceTasks=1时,不管MapTask端输出多少个分区文件,最终结果都交给这一个ReduceTask,最终也就只会产生一个结果文件 part-r-00000:
请添加图片描述

1.1.4 WritableComparable排序

(1)排序概述

请添加图片描述

请添加图片描述

对于MapTask,它会将处理的结果暂时放到环形缓冲区中,当环形缓冲区使用率达到一定阈值后,再对缓冲区中的数据进行一次快速排序,并将这些有序数据溢写到磁盘上,而当数据处理完毕后,它会对磁盘上所有文件进行归并排序

对于ReduceTask,它从每个MapTask上远程拷贝相应的数据文件,如果文件大小超过一定阈值,则溢写磁盘上,否则存储在内存中。如果磁盘上文件数目达到一定阈值,则进行一次归并排序以生成一个更大文件;如果内存中文件大小或者数目超过一定阈值,则进行一次合并后将数据溢写到磁盘上。当所有数据拷贝完
毕后,ReduceTask统一对内存和磁盘上的所有数据进行一次归并排序

(2)排序分类

1)部分排序
MapReduce根据输入记录的键对数据集排序。保证输出的每个文件内部有序

2)全排序
最终输出结果只有一个文件,且文件内部有序。实现方式是只设置一个ReduceTask。但该方法在
处理大型文件时效率极低,因为一台机器处理所有文件,完全丧失了MapReduce所提供的并行架构。

3)辅助排序:(GroupingComparator分组)
在Reduce端对key进行分组。应用于:在接收的key为bean对象时,想让一个或几个字段相同(全部
字段比较不相同)的key进入到同一个reduce方法时,可以采用分组排序。

4)二次排序
在自定义排序过程中,如果compareTo中的判断条件为两个即为二次排序。

(3)自定义排序WritableComparable 原理分析

bean 对象做为 key 传输,需要实现 WritableComparable 接口重写 compareTo方法,就可以实现排序。

@Override
public int compareTo(FlowBean bean) {
	int result;
	// 按照总流量大小,倒序排列
	if (this.sumFlow > bean.getSumFlow()) {
	result = -1;
	}else if (this.sumFlow < bean.getSumFlow()) {
	result = 1;
	}else {
	result = 0;
	}
	return result;
}

1.1.5 WritableComparable排序案例实操(全排序)

1)需求

根据序列化案例产生的结果再次对总流量进行倒序排序。

(1)输入数据

输入数据为 Hadoop框架—Hadoop序列化操作后输出的文件part-r-00000:

请添加图片描述

注:序列化案例输出的文件中还有其他的CRC校验文件SUCCESS文件,在本节作为Map阶段的输入时吗,要把这些文件全部删除只剩下part-r-00000文件!

(2)期望输出数据

请添加图片描述

2)需求分析

请添加图片描述

3)代码实现

(1)FlowBean 对象在在需求 1 基础上增加了比较功能(compareTo方法):

package com.root.mapreduce.writablecomparable;

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> {
    private long upFlow;//上行流量
    private long downFlow;//下行流量
    private long sumFlow;//总流量

    //空参构造
    public FlowBean() {
    }

    public long getUpFlow() {
        return upFlow;
    }

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

    public long getDownFlow() {
        return downFlow;
    }

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

    public long getSumFlow() {
        return sumFlow;
    }

    public void setSumFlow(long sumFlow) {
        this.sumFlow = sumFlow;
    }
    public void setSumFlow() {
        this.sumFlow = this.upFlow+this.downFlow;
    }

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

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

    @Override
    public int compareTo(FlowBean o) {
    //按照总流量大小倒序排列
        if(this.sumFlow>o.sumFlow){
            return -1;
        } else if (this.sumFlow<o.sumFlow) {
            return 1;
        }else{
            return 0;
        }
    }

    public String toString(){
        return upFlow+"\t"+downFlow+"\t"+sumFlow;
    }
}

(2)编写 Mapper 类

package com.root.mapreduce.writablecomparable;

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

import java.io.IOException;

//注意此处的Mapper内的泛型输出类型做了改变,
//因为要按照FlowBean对象的sumFlow属性排序,所以FlowBean对象要当作key值
public class FlowMapper extends Mapper<LongWritable, Text, FlowBean,Text> {
    private FlowBean outK=new FlowBean();
    private Text outV=new Text();
    @Override
    protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, FlowBean, Text>.Context context) throws IOException, InterruptedException {
       //获取一行
        String s = value.toString();

        //切割data
        String[] split = s.split("\t");

        //封装
        outK.setUpFlow(Long.parseLong(split[1]));
        outK.setDownFlow(Long.parseLong(split[2]));
        outK.setSumFlow();
        outV.set(split[0]);

        //写出
        context.write(outK,outV);

    }
}

(3)编写 Reducer 类

package com.root.mapreduce.writablecomparable;

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

import java.io.IOException;

public class FlowReducer extends Reducer<FlowBean,Text,Text, FlowBean> {

    @Override
    protected void reduce(FlowBean key, Iterable<Text> values, Reducer<FlowBean, Text, Text, FlowBean>.Context context) throws IOException, InterruptedException {
    	//遍历 values 集合,循环写出,避免总流量相同的情况
        for (Text value : values) {
        //调换 KV 位置,反向写出
            context.write(value,key);
        }
    }
}

(4)编写 Driver 类

package com.root.mapreduce.writablecomparable;

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;

import java.io.IOException;

public class FlowDriver {
    public static void main(String[] args) throws IOException, InterruptedException, ClassNotFoundException {
        //1.获取job
        Configuration conf = new Configuration();
        Job ins = Job.getInstance(conf);

        //2.设置jar包路径
        ins.setJarByClass(FlowDriver.class);

        //3.关联mapper和reducer
        ins.setMapperClass(FlowMapper.class);
        ins.setReducerClass(FlowReducer.class);

        //4.设置map输出的kv类型
        //此处map输出的k是FlowBean类型(对象),v是Text类型(手机号)
        ins.setMapOutputKeyClass(FlowBean.class);
        ins.setMapOutputValueClass(Text.class);

        //5.设置最终输出的kv类型
        ins.setOutputKeyClass(Text.class);
        ins.setOutputValueClass(FlowBean.class);

        //6.设置输入路径和输出路径
        FileInputFormat.setInputPaths(ins, new Path("D:\\java_learning\\output\\outputflow"));
        FileOutputFormat.setOutputPath(ins, new Path("D:\\java_learning\\output\\outputflow2"));

        //7.提交job
        boolean result = ins.waitForCompletion(true);

        System.exit(result ? 0 : 1);

    }
}

(5)输出结果展示:

请添加图片描述

如果想二次排序,比如当存在多条数据总流量相同的情况时,我们想继续按照上行/下行流量的大小再排序,可以修改FlowBean类中的重写的compareTo方法实现:
请添加图片描述

1.1.6 WritableComparable排序案例实操(区内排序)

1)需求

要求每个省份手机号输出的文件中按照总流量内部排序。

2)需求分析

基于前一个需求,增加自定义分区类,分区按照省份手机号设置。
请添加图片描述

3)案例实操

(1)增加自定义分区类

在全排序的Mapper类,Reducer类,Driver类的基础上,增添一个自定义分区类

package com.root.mapreduce.writablecomparable2;

import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Partitioner;

public class NewPartitioner extends Partitioner<FlowBean,Text> {

    @Override
    public int getPartition(FlowBean flowBean, Text text, int i) {
        //获取手机号前三位
        String s = text.toString();
        String result = s.substring(0, 3);

        //设定分区规则
        switch (result){
            case "136":return 0;
            case "137":return 1;
            case "138":return 2;
            case "139":return 3;
            default:return 4;
        }
    }
}

(2)在驱动类中添加分区类

// 设置自定义分区器
job.setPartitionerClass(ProvincePartitioner2.class);
// 设置对应的 ReduceTask 的个数
job.setNumReduceTasks(5);

(3)输出结果展示:

请添加图片描述

1.1.7 Combiner合并

请添加图片描述

自定义 Combiner 实现步骤
(a)自定义一个 Combiner 继承 Reducer,重写 Reduce 方法

public class WordCountCombiner extends Reducer<Text, IntWritable, Text, IntWritable> {
 private IntWritable outV = new IntWritable();
 	@Override
 	protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
 	int sum = 0;
 	for (IntWritable value : values) {
 	sum += value.get();
 	}
 
 	outV.set(sum);
 
 	context.write(key,outV);
 	}
}

(b)在 Job 驱动类中设置:

job.setCombinerClass(WordCountCombiner.class);

1.1.8 Combiner合并案例实操

1)需求

统计过程中对每一个 MapTask 的输出进行局部汇总,以减小网络传输量即采用Combiner 功能

(1)数据输入
请添加图片描述
(2)期望输出数据

期望:Combine 输入数据多,输出时经过合并,输出数据降低。

2)需求分析

请添加图片描述

3)案例实操-方案一

(1)增加一个 WordCountCombiner 类继承 Reducer

package com.root.mapreduce.combiner;

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

import java.io.IOException;

public class WordcountCombiner extends Reducer<Text, IntWritable,Text,IntWritable> {
    //combiner在map阶段 所有map输出的k,v就是其输入的k,v
    private IntWritable outV=new IntWritable();

    @Override
    protected void reduce(Text key, Iterable<IntWritable> values, Reducer<Text, IntWritable, Text, IntWritable>.Context context) throws IOException, InterruptedException {
        int sum=0;
        for (IntWritable value : values) {
            sum+=value.get();
        }

        //封装
        outV.set(sum);

        //写出
        context.write(key,outV);

    }
}

(2)在 WordcountDriver 驱动类中指定 Combiner

// 指定需要使用 combiner,以及用哪个类作为 combiner 的逻辑
ins.setCombinerClass(WordcountCombiner.class);

4)案例实操-方案二

(1)将 WordcountReducer 作为 Combiner 在 WordcountDriver 驱动类中指定

// 指定需要使用 Combiner,以及用哪个类作为 Combiner 的逻辑
ins.setCombinerClass(WordCountReducer.class);

运行程序,如下图所示:

请添加图片描述请添加图片描述

因为:
请添加图片描述
所以Combine input records=10;

因为:
请添加图片描述

所以Combine output records=7;

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

丷江南南

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值