7月31日

Shuffle 机制 

Map 方法之后,Reduce 方法之前的数据处理过程称之为 Shuffle。

我自己理解成一个将数据清洗整理的过程 ,用快排 通过索引就是key 按字典顺序来排序

 

 

以下都是在shuffle中的操作: 

 

Partition分区

求将结果按照条件输出到不同文件中(分区),这一步在流程上是在map的输出过程中执行的 

 

在此处打断点,进入源码来探究

 

 

自定义设置分区

 

第一步 自定义分区类  按要求自定义

 

 

例如:我按电话号码前三位来进行分区

public class ProvincePartitioner extends Partitioner<Text, FlowBean> {
    @Override
    public int getPartition(Text text, FlowBean flowBean, int i) {

        String phone = text.toString();
        String prePhone = phone.substring(0, 3);//包左边不包右
       
//定义一个分区号变量 partition,根据 prePhone 设置分区号
       
int partition;
        if("152".equals(prePhone)){
            partition = 0;
        }else if("183".equals(prePhone)){
            partition = 1;
        }else if("137".equals(prePhone)){
            partition = 2;
        }else {
            partition = 3;
        }
        //最后返回分区号 partition
       
return partition;




    }
}

 

第二步

在driver设置分区类

job.setPartitionerClass(ProvincePartitioner.class);

 

第三步

在driver设置ReduceTask数量
job.setNumReduceTasks(4);

 

分区注意点:

(1)     如果ReduceTask的数量> getPartition的结果数,则会多产生几个空的输出文件part-r-000xx;

(2)     如果1<ReduceTask的数量<getPartition的结果数,则有一部分分区数据无处安放,会Exception;

(3)     如 果ReduceTask的数量=1,则不管MapTask端输出多少个分区文件,最终结果都交给这一个 ReduceTask,最终也就只会产生一个结果文件 part-r-00000;

(4)     分区号必须从零开始,逐一累加。

 

 

做自定义排序

 

Map结束后reduce之前会有一个排序 ,一般是默认排序

可以根据自己需要手动排序

 

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

这里就在前处理好的结果 文件当作输入文件(该文件已经没有重复手机号码)

 

在flowBean中增加排序内容,因为bean要做key值来进行操作,需要比较和序列化接口

 

注意比较方法的写法,和mapper reducer的泛型

 

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.downFlow+this.upFlow;
    }
    @Override
    public void write(DataOutput out) throws IOException {
        out.writeLong(upFlow);
        out.writeLong(downFlow);
        out.writeLong(sumFlow);
    }

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

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

    @Override
    public int compareTo(FlowBean o) {
        //按照总流量的倒序进行排序
       
if(this.sumFlow > o.sumFlow){  //我理解this相当于形参
           
return -1;
        }else if(this.sumFlow < o.sumFlow){
            return 1;
        }else {
            //在之前排序条件下按上行流量正序排
           
if(this.upFlow>o.upFlow){
                return 1;
            }else if (this.upFlow<o.upFlow){
                return -1;
            }else {
                return 0;
            }
        }
    }
}

 

 

 

修改mapper 和reducer

public class FlowMapper extends Mapper<LongWritable, Text,FlowBean,Text> {
    private Text outV=new Text();
    private FlowBean outK=new FlowBean();
    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        //获取一行
       
String line = value.toString();
        //切割
       
String[] split = line.split("\t");
        //封装
       
outK.setUpFlow(Long.parseLong(split[1]));
        outK.setDownFlow(Long.parseLong(split[2]));
        outK.setSumFlow();
        outV.set(split[0]);
        // 写出 outK outV
       
context.write(outK,outV);
    }
}

 

 

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

    @Override
    protected void reduce(FlowBean key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
        //遍历 values 集合,循环写出,避免总流量相同的情况
       
//注意 经过上一次加工这里已经没有电话号码重复的了
       
for (Text value : values) {
            //调换 KV 位置,反向写出
           
context.write(value,key);
        }
    }
}

 

 

Combiner

这个可以有可以没有,目的是减轻reduce工作量

(1)Combiner是MR程序中Mapper和Reducer之外的一种组件。

(2)Combiner组件的父类就是Reducer。

(3)Combiner和Reducer的区别在于运行的位置 Combiner是在每一个MapTask所在的节点运行;

(4)Combiner的意义就是对每一个MapTask的输出进行局部汇总,以减小网络传输量。 (5)Combiner能够应用的前提是不能影响最终的业务逻辑,而且,Combiner的输出kv 应该跟Reducer的输入kv类型要对应起来。

 Mapper 3 5 7 ->(3+5+7)/3=5 2 6 ->(2+6)/2=4 Reducer (3+5+7+2+6)/5=23/5 不等于 (5+4)/2=9/2

 

 

让数据在map就变成这样

 

 

 

 

自定义一个 Combiner 继承 Reducer,重写 Reduce 方法

public class WordCountCombiner extends Reducer {

private IntWritable outV = new IntWritable();

@Override

protected void reduce(Text key, Iterable 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);

 

 

 

因为该文件代码和想实现的功能与reduce一模一样 所以想使用这个组件可以直接这样使用

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

 

 学习时间:9:32 到11:35   12:15到15:23

posted @ 2021-07-31 19:01  不咬牙  阅读(56)  评论(0)    收藏  举报