一、概述
   
MapReduce框架对处理结果的输出会根据key值进行默认的排序,这个默认排序可以满足一部分需求,但是也是十分有限的。在我们实际的需求当中,往
往有要对reduce输出结果进行二次排序的需求。对于二次排序的实现,本文将通过一个实际的MapReduce二次排序例子讲述
二次排序的实现和其MapReduce的整个处理流程,并且通过结果和map、reduce端的日志来验证所描述的处理流程的正确性。
二、需求描述
1、输入数据:
sort1    1
sort2    3
sort2    77
sort2    54
sort1    2
sort6    22
sort6    221
sort6    20
2、目标输出
sort1 1,2
sort2 3,54,77
sort6 20,22,221

三、解决思路
1、首先,在思考解决问题思路时,我们先应该深刻的理解MapReduce处理数据的整个流程,这是最基础的,不然的话是不可能找到解决问题的思路的,我描述一下MapReduce处理数据的大概简单流程:首先,MapReduce框架通过getSplit方法实现对原始文件的切片之后,每一个切片对应着一个map
task,inputSplit输入到Map函数进行处理,中间结果经过环形缓冲区的排序,然后分区、自定义二次排序(如果有的话)和合并,再通过
shuffle操作将数据传输到reduce
task端,reduce端也存在着缓冲区,数据也会在缓冲区和磁盘中进行合并排序等操作,然后对数据按照Key值进行分组,然后没处理完一个分组之后就
会去调用一次reduce函数,最终输出结果。大概流程我画了一下,如下图:

2、具体解决思路

(1)Map端处理:

   根据上面的需求,我们有一个非常明确的目标就是要对第一列相同的记录合并,并且对合并后的数字进行排序。我们都知道MapReduce框架不管是默认排序或者是自定义排序都只是对Key值进行排序,现在的情况是这些数据不是key值,怎么办?其实我们可以将原始数据的Key值和其对应的数据组合成一个新的Key值,然后新的Key值对应的还是之前的数字。那么我们就可以将原始数据的map输出变成类似下面的数据结构:
{[sort1,1],1}
{[sort2,3],3}
{[sort2,77],77}
{[sort2,54],54}
{[sort1,2],2}
{[sort6,22],22}
{[sort6,221],221}
{[sort6,20],20}
那么我们只需要对[]里面的新key值进行排序就ok了。然后我们需要自定义一个分区处理器,因为我的目标不是想将新key相同的传到同一个reduce中,而是想将新key中的第一个字段相同的才放到同一个reduce中进行分组合并,所以我们需要根据新key值中的第一个字段来自定义一个分区处理器。通过分区操作后,得到的数据流如下:
Partition1:{[sort1,1],1}、{[sort1,2],2}
Partition2:{[sort2,3],3}、{[sort2,77],77}、{[sort2,54],54}
Partition3:{[sort6,22],22}、{[sort6,221],221}、{[sort6,20],20}
分区操作完成之后,我调用自己的自定义排序器对新的Key值进行排序。
{[sort1,1],1}
{[sort1,2],2}
{[sort2,3],3}
{[sort2,54],54}
{[sort2,77],77}
{[sort6,20],20}
{[sort6,22],22}
{[sort6,221],221}
(2)Reduce端处理:
    经过Shuffle处理之后,数据传输到Reducer端了。在Reducer端对按照组合键的第一个字段来进行分组,并且每处理完一次分组之后就会调用一次reduce函数来对这个分组进行处理输出。最终的各个分组的数据结构变成类似下面的数据结构:
{sort1,[1,2]}
{sort2,[3,54,77]}
{sort6,[20,22,221]}

四、具体实现
1、自定义组合键

  1. package com.mr;
  2. import java.io.DataInput;
  3. import java.io.DataOutput;
  4. import java.io.IOException;
  5. import org.apache.Hadoop.io.IntWritable;
  6. import org.apache.hadoop.io.Text;
  7. import org.apache.hadoop.io.WritableComparable;
  8. import org.slf4j.Logger;
  9. import org.slf4j.LoggerFactory;
  10. /**
  11. * 自定义组合键
  12. * @author zenghzhaozheng
  13. */
  14. public class CombinationKey implements WritableComparable<CombinationKey>{
  15. private static final Logger logger = LoggerFactory.getLogger(CombinationKey.class);
  16. private Text firstKey;
  17. private IntWritable secondKey;
  18. public CombinationKey() {
  19. this.firstKey = new Text();
  20. this.secondKey = new IntWritable();
  21. }
  22. public Text getFirstKey() {
  23. return this.firstKey;
  24. }
  25. public void setFirstKey(Text firstKey) {
  26. this.firstKey = firstKey;
  27. }
  28. public IntWritable getSecondKey() {
  29. return this.secondKey;
  30. }
  31. public void setSecondKey(IntWritable secondKey) {
  32. this.secondKey = secondKey;
  33. }
  34. @Override
  35. public void readFields(DataInput dateInput) throws IOException {
  36. // TODO Auto-generated method stub
  37. this.firstKey.readFields(dateInput);
  38. this.secondKey.readFields(dateInput);
  39. }
  40. @Override
  41. public void write(DataOutput outPut) throws IOException {
  42. this.firstKey.write(outPut);
  43. this.secondKey.write(outPut);
  44. }
  45. /**
  46. * 自定义比较策略
  47. * 注意:该比较策略用于mapreduce的第一次默认排序,也就是发生在map阶段的sort小阶段,
  48. * 发生地点为环形缓冲区(可以通过io.sort.mb进行大小调整)
  49. */
  50. @Override
  51. public int compareTo(CombinationKey combinationKey) {
  52. logger.info("-------CombinationKey flag-------");
  53. return this.firstKey.compareTo(combinationKey.getFirstKey());
  54. }
  55. }

说明:在自定义组合键的时候,我们需要特别注意,一定要实现WritableComparable接口,并且实现compareTo方法的比较策略。这个 用于mapreduce的第一次默认排序,也就是发生在map阶段的sort小阶段,发生地点为环形缓冲区(可以通过io.sort.mb进行大小调 整),但是其对我们最终的二次排序结果是没有影响的。我们二次排序的最终结果是由我们的自定义比较器决定的。
2、自定义分区器

  1. package com.mr;
  2. import org.apache.hadoop.io.IntWritable;
  3. import org.apache.hadoop.mapreduce.Partitioner;
  4. import org.slf4j.Logger;
  5. import org.slf4j.LoggerFactory;
  6. /**
  7. * 自定义分区
  8. * @author zengzhaozheng
  9. */
  10. public class DefinedPartition extends Partitioner<CombinationKey,IntWritable>{
  11. private static final Logger logger = LoggerFactory.getLogger(DefinedPartition.class);
  12. /**
  13. * 数据输入来源:map输出
  14. * @author zengzhaozheng
  15. * @param key map输出键值
  16. * @param value map输出value值
  17. * @param numPartitions 分区总数,即reduce task个数
  18. */
  19. @Override
  20. public int getPartition(CombinationKey key, IntWritable value,int numPartitions) {
  21. logger.info("--------enter DefinedPartition flag--------");
  22. /**
  23. * 注意:这里采用默认的hash分区实现方法
  24. * 根据组合键的第一个值作为分区
  25. * 这里需要说明一下,如果不自定义分区的话,mapreduce框架会根据默认的hash分区方法,
  26. * 将整个组合将相等的分到一个分区中,这样的话显然不是我们要的效果
  27. */
  28. logger.info("--------out DefinedPartition flag--------");
  29. return (key.getFirstKey().hashCode()&Integer.MAX_VALUE)%numPartitions;
  30. }
  31. }

说明:具体说明看代码注释。

3、自定义比较器

  1. package com.mr;
  2. import org.apache.hadoop.io.WritableComparable;
  3. import org.apache.hadoop.io.WritableComparator;
  4. import org.slf4j.Logger;
  5. import org.slf4j.LoggerFactory;
  6. /**
  7. * 自定义二次排序策略
  8. * @author zengzhaoheng
  9. */
  10. public class DefinedComparator extends WritableComparator {
  11. private static final Logger logger = LoggerFactory.getLogger(DefinedComparator.class);
  12. public DefinedComparator() {
  13. super(CombinationKey.class,true);
  14. }
  15. @Override
  16. public int compare(WritableComparable combinationKeyOne,
  17. WritableComparable CombinationKeyOther) {
  18. logger.info("---------enter DefinedComparator flag---------");
  19.  
  20. CombinationKey c1 = (CombinationKey) combinationKeyOne;
  21. CombinationKey c2 = (CombinationKey) CombinationKeyOther;
  22.  
  23. /**
  24. * 确保进行排序的数据在同一个区内,如果不在同一个区则按照组合键中第一个键排序
  25. * 另外,这个判断是可以调整最终输出的组合键第一个值的排序
  26. * 下面这种比较对第一个字段的排序是升序的,如果想降序这将c1和c2���过来(假设1)
  27. */
  28. if(!c1.getFirstKey().equals(c2.getFirstKey())){
  29. logger.info("---------out DefinedComparator flag---------");
  30. return c1.getFirstKey().compareTo(c2.getFirstKey());
  31. }
  32. else{//按照组合键的第二个键的升序排序,将c1和c2倒过来则是按照数字的降序排序(假设2)
  33. logger.info("---------out DefinedComparator flag---------");
  34. return c1.getSecondKey().get()-c2.getSecondKey().get();//0,负数,正数
  35. }
  36. /**
  37. * (1)按照上面的这种实现最终的二次排序结果为:
  38. * sort1 1,2
  39. * sort2 3,54,77
  40. * sort6 20,22,221
  41. * (2)如果实现假设1,则最终的二次排序结果为:
  42. * sort6 20,22,221
  43. * sort2 3,54,77
  44. * sort1 1,2
  45. * (3)如果实现假设2,则最终的二次排序结果为:
  46. * sort1 2,1
  47. * sort2 77,54,3
  48. * sort6 221,22,20
  49. */
  50. }
  51. }

说明:自定义比较器决定了我们二次排序的结果。自定义比较器需要继承WritableComparator类,并且重写compare方法实现自己的比较策略。具体的排序问题请看注释。

Hadoop学习之自定义二次排序的更多相关文章

  1. MapReduce自定义二次排序流程

    每一条记录开始是进入到map函数进行处理,处理完了之后立马就入自定义分区函数中对其进行分区,当所有输入数据经过map函数和分区函数处理完之后,就调用自定义二次排序函数对其进行排序. MapReduce ...

  2. 分别使用Hadoop和Spark实现二次排序

    零.序(注意本部分与标题无太大关系,可直接调至第一部分) 既然没用为啥会有序?原因不想再开一篇文章,来抒发点什么感想或者计划了,就在这里写点好了: 前些日子买了几本书,打算学习和研究大数据方面的知识, ...

  3. python 实现Hadoop的partitioner和二次排序

    我们知道,一个典型的Map-Reduce过程包 括:Input->Map->Partition->Reduce->Output. Partition负责把Map任务输出的中间结 ...

  4. Hadoop学习之旅二:HDFS

    本文基于Hadoop1.X 概述 分布式文件系统主要用来解决如下几个问题: 读写大文件 加速运算 对于某些体积巨大的文件,比如其大小超过了计算机文件系统所能存放的最大限制或者是其大小甚至超过了计算机整 ...

  5. hadoop学习笔记(二):简单启动

    一.hadoop组件依赖关系 二.hadoop日志格式: 两种日志,分别以out和log结尾: 1 以log结尾的日志:通过log4j日志记录格式进行记录的日志,采用日常滚动文件后缀策略来命名日志文件 ...

  6. 【Hadoop学习之十二】MapReduce案例分析四-TF-IDF

    环境 虚拟机:VMware 10 Linux版本:CentOS-6.5-x86_64 客户端:Xshell4 FTP:Xftp4 jdk8 hadoop-3.1.1 概念TF-IDF(term fre ...

  7. C# Hadoop学习笔记(二)—架构原理

    一,架构   二.名词解释 (一)NameNode(简称NN),Hadoop的主节点,负责侦听节点是否活跃,对外开放接口等.在未来的大数据处理过程中,由于访问量和节点数量的不断增多,需要该节点的处理能 ...

  8. hadoop学习笔记(二):centos7三节点安装hadoop2.7.0

    环境win7+vamvare10+centos7 一.新建三台centos7 64位的虚拟机 master node1 node2 二.关闭三台虚拟机的防火墙,在每台虚拟机里面执行: systemct ...

  9. C#学习之自定义数组及其排序

    在C#中对数组的定义比较灵活.这里着重说一下自定义数组和Array类的排序. 在Array类中通过属性Length就可以获取整个数组中数据的数量,可以通过foreach迭代数组. 使用Rank属性可以 ...

随机推荐

  1. 安装rac遇到的问题总结:

    1. 选择虚拟机工具 这个过程是非常的波折.这次安装也让我吸取了很大教训,获得了宝贵经验. 首先啊,必须了解rac的机制. 共享磁盘+多实例. 这就意味着,我们必须使用一个支持共享磁盘的虚拟机. 第一 ...

  2. 64位ubuntu编译32位程序

      最近在64位ubuntu上开发,需要编译32位程序,需要安装这两个包,然后在编译器参数加上-m32.不放心的话可以用ldd或file查看一下是否生成了对应位数的程序. $ apt-get inst ...

  3. java面向对象之 继承 Inheritance

    对象的一个新类可以从现有的类中派生,这个过程称为类继承.新类继承了原始类的特性,新类称为原始类的派生类(子类),而原始类称为新类的基类(父类).派生类可以从它的基类那里继承方法和实例变量,并且类可以修 ...

  4. GoF——组合模式

    组合模式:将对象组合成树形结构以表示“部分-真题”的结构层次.组合模式使得用户对单个对象和组合对象的使用具有一致性. 结构图: using System; using System.Collectio ...

  5. vb listview 的常用操作

    常用操作:获取当前行数和列数: MsgBox "行数:" & ListView1.ListItems.Count & "列数:" & L ...

  6. row_number() over (partition by....order by...)用法 分组排序

    row_number() over (partition by....order by...)用法 分组排序 row_number() OVER (PARTITION BY COL1 ORDER BY ...

  7. Delphi下TLabel鼠标MouseEnter、MouseLeave更改颜色失灵

    在Delphi 7下,如果想在鼠标MouseEnter.MouseLeave的时候改变TLabel自身的颜色,很多人可能会采用 Label.Color := clRed;这样的方式来实现,我当初也是一 ...

  8. 改进的延时函数Delay(使用MsgWaitForMultipleObjects等待消息或超时的到来)

    解决上一节中延时函数占CPU使用率(达50%)的第二种方法是利用消息机制,通过API函数MsgWaitForMultipleObjects等待消息或超时的到来,从而避免使用循环检测使CPU占用率过高. ...

  9. Day1_PHP快速入门

    本人知识背景:行业软件C/C++开发两年经验,了解PHP, 所以学习日志偏向记录PHP相对于C的特性 测试环境:EasyPHP13.1 Day 1 学习时间:3小时 1. HTML触发PHP HTML ...

  10. #include <vector>

    双端队列deque比向量vector更有优势 vector是动态数组,在堆上 vector比array更常用 不需要变长,容量较小,用array 需要变长,容量较大,用vector 1 at() 取出 ...