【hadoop】23.MapReduce-shuffle之排序

简介

排序是MapReduce框架中最重要的操作之一。

在Mapper和Reducer阶段都有涉及,Map Task和Reduce Task均会对数据(按照key)进行排序。该操作属于Hadoop的默认行为。任何应用程序中的数据均会被排序,而不管逻辑上是否需要。

对于Map Task,它会将处理的结果暂时放到一个缓冲区中,当缓冲区使用率达到一定阈值后,再对缓冲区中的数据进行一次排序,并将这些有序数据写到磁盘上,而当数据处理完毕后,它会对磁盘上所有文件进行一次合并,以将这些文件合并成一个大的有序文件。

对于Reduce Task,它从每个Map Task上远程拷贝相应的数据文件,如果文件大小超过一定阈值,则放到磁盘上,否则放到内存中。

如果磁盘上文件数目达到一定阈值,则进行一次合并以生成一个更大文件;如果内存中文件大小或者数目超过一定阈值,则进行一次合并后将数据写到磁盘上。当所有数据拷贝完毕后,Reduce Task统一对内存和磁盘上的所有数据进行一次合并。

1、排序分类

1、部分排序

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

2、全排序

如何用Hadoop产生一个全局排序的文件?最简单的方法是使用一个分区。但该方法在处理大型文件时效率极低,因为一台机器必须处理所有输出文件,从而完全丧失了MapReduce所提供的并行架构。

全排序的解决方案:

  • 首先创建一系列排好序的文件;
  • 其次,串联这些文件;
  • 最后,生成一个全局排序的文件。

主要思路是使用一个分区来描述输出的全局排序。例如:可以为上述文件创建3个分区,在第一分区中,记录的单词首字母a-g,第二分区记录单词首字母h-n, 第三分区记录单词首字母o-z。

3、辅助排序:(GroupingComparator分组)

Mapreduce框架在记录到达reducer之前按键对记录排序,但键所对应的值并没有被排序。甚至在不同的执行轮次中,这些值的排序也不固定,因为它们来自不同的map任务且这些map任务在不同轮次中完成时间各不相同。

一般来说,大多数MapReduce程序会避免让reduce函数依赖于值的排序。但是,有时也需要通过特定的方法对键进行排序和分组等以实现对值的排序。

2、自定义排序

很多时候,默认的排序逻辑并不能满足我们的需求,例如流量Bean的排序。我们需要自定义排序逻辑。

原理很简单:对象实现WritableComparable接口重写compareTo方法,就可以实现排序。

public int compareTo(FlowBean o) {
	// 倒序排列,从大到小
	return this.sumFlow > o.getSumFlow() ? -1 : 1;
}

3、排序案例:汇总流量排序输出

我们现在有了这样的需求,需要将手机流量的输出结果集不按默认的手机号码序输出,而是按照汇总流量从高到低输出。

1、分析

首先要明确的是,排序只能针对key进行,而我们的汇总流量属于Val,因此,我们需要将FlowBean作为key进行中间排序。需要重新定义一个MapReducer任务。

2、编写代码

(1)创建一个新的模块,phone-flow-sort。

(2)编写流量Bean,和之前的Bean差不多,只不过实现了排序接口

import org.apache.hadoop.io.Writable;
import org.apache.hadoop.io.WritableComparable;
import org.apache.hadoop.yarn.webapp.hamlet.HamletSpec;

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

public class FlowBean implements Writable, WritableComparable<FlowBean> {
    ...
    // desc sort.
    public int compareTo(FlowBean other) {
        return this.getTotalFlow()>other.getTotalFlow()? -1:1;
    }
    ...
}

(3)Mapper

package com.zhaoyi.flowbeansort;

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

import java.io.IOException;

public class FlowSortMapper extends Mapper<LongWritable, Text, FlowBean, Text> {
    FlowBean bean = new FlowBean();
    Text phone = new Text();

    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        String line = value.toString();
        String[] strings = line.split("\t");
        // Key-FlowBean: get the up and downFlow.
        bean.set(Long.parseLong(strings[1]), Long.parseLong(strings[2]));
        // Val-Phone
        phone.set(value);
        context.write(bean, phone);
    }
}

需要明确的是,我们当前的输入文件即为phoneflow项目的输出结果,其结构如下

13480253104	180	180	360
13502468823	7335	110349	117684
13560436666	1116	954	2070
13560439658	2034	5892	7926
13602846565	1938	2910	4848
13660577991	6960	690	7650
13719199419	240	0	240
13726230503	2481	24681	27162
13726238888	2481	24681	27162
13760778710	120	120	240
13826544101	264	0	264
13922314466	3008	3720	6728
13925057413	11058	48243	59301
13926251106	240	0	240
13926435656	132	1512	1644
15013685858	3659	3538	7197
15920133257	3156	2936	6092
15989002119	1938	180	2118
18211575961	1527	2106	3633
18320173382	9531	2412	11943
84138413	4116	1432	5548

因此,相对于当前的Mapper,输入类型为行号,Val为Text,即一行数据。现在,我们只需将FlowBean作为Mapper的key输出,电话号码作为val。

然后在reducer中将他们颠倒,即可完成我们的需求。

(4)Reducer

package com.zhaoyi.flowbeansort;

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

import java.io.IOException;

public class FlowSortReducer extends Reducer<FlowBean, Text, Text, FlowBean> {
    @Override
    protected void reduce(FlowBean key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
        // the values must be only one, and we just need upside down key-val.
        context.write(values.iterator().next(), key);
    }
}

(5)Driver

package com.zhaoyi.flowbeansort;

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 FlowSortDriver {
    public static void main(String[] args) throws Exception {
        Job job = Job.getInstance(new Configuration());

        job.setJarByClass(FlowSortDriver.class);

        job.setMapperClass(FlowSortMapper.class);
        job.setReducerClass(FlowSortReducer.class);
       
        job.setMapOutputKeyClass(FlowBean.class);
        job.setMapOutputValueClass(Text.class);

        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(FlowBean.class);


        FileInputFormat.setInputPaths(job, new Path(args[0]));
        FileOutputFormat.setOutputPath(job, new Path(args[1]));

        System.exit(job.waitForCompletion(true)? 1:0);

    }
}

由于Mapper与Reducer的输出类型不一样,因此需要指定Map的输出k-v类型,不能像之前的那样省略这里的代码了。

配置参数program arg,以之前的项目的输出作为输入。运行项目,将输出结果生成到output2中。

D:\hadoop\output D:\hadoop\output2

查看结果分区文件

13502468823	7335	110349	117684	7335	110349	117684
13925057413	11058	48243	59301	11058	48243	59301
13726238888	2481	24681	27162	2481	24681	27162
13726230503	2481	24681	27162	2481	24681	27162
18320173382	9531	2412	11943	9531	2412	11943
13560439658	2034	5892	7926	2034	5892	7926
13660577991	6960	690	7650	6960	690	7650
15013685858	3659	3538	7197	3659	3538	7197
13922314466	3008	3720	6728	3008	3720	6728
15920133257	3156	2936	6092	3156	2936	6092
84138413	4116	1432	5548	4116	1432	5548
13602846565	1938	2910	4848	1938	2910	4848
18211575961	1527	2106	3633	1527	2106	3633
15989002119	1938	180	2118	1938	180	2118
13560436666	1116	954	2070	1116	954	2070
13926435656	132	1512	1644	132	1512	1644
13480253104	180	180	360	180	180	360
13826544101	264	0	264	264	0	264
13926251106	240	0	240	240	0	240
13760778710	120	120	240	120	120	240
13719199419	240	0	240	240	0	240

实现了我们最终的需求。

可以看到,整个过程我们其实只是定义了FlowBean的排序逻辑,同时将其作为key而已(具体来说,是Mapper的输出key)。

转载于:https://my.oschina.net/u/3091870/blog/3000622

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值