MapReduce排序,分区,分组总结

1. 分区

1.1 自定义分区器

 1) 自定义分区器继承 Partitioner类,并重写getPartition方法
 2) 在driver类中设置使用
    job.setPartitionerClass(PhoneNumPartitioner.class);
 3) 设置reduce的个数
    正常情况下,根据分区器业务来决定设置多少个,说白了就是分区器的逻辑会生成多少个分区,
则设置的多少个reduce

1.2 分区使用的注意事项:

 1). reduce个数的设置

 如果不设置,reduce的个数默认为1 , 则最终的分区号是固定的 0 
 如果    1 <   reduce个数  < 分区数, 报错 
 如果 reduce个数  > 分区数,  不报错, 多出的reduce空跑一趟.
 最佳: reduce的个数就设置为实际的分区数.

 2). 分区号只能从0开始,逐一累加

2. 排序

2.1 排序是MR中最重要的操作之一 。

   在MapTask中有两次排序 溢写前排序  归并排序
   在ReduceTask中有1次排序  归并排序

2.2 在MR中,排序是默认的行为. 默认会对KV中的K进行排序.

   默认采用字典序进行排序.

2.3 排序的分类:

   1) 全排序     所有的数据整体排序,要求只能有一个分区,一个reduce.
   2) 区内排序   每个分区内的数据整体排序.
   3) 辅助排序(分组排序) 
   4) 二次排序   比较规则中用到两个条件

2.4 排序的前提:

   排序的前提就是能够进行比较,
   Java中的比较接口和比较器:
	Comparable: 比较接口, compareTo()
	Compartor : 比较器 ,  compare()

2.5 Hadoop排序时 如何比较的

   例如:  
   
   在MapTask中的 MapOutputBuffer 类中的init方法中
   
   comparator = job.getOutputKeyComparator();  // 获取key的比较器对象

  public RawComparator getOutputKeyComparator() {
     Class<? extends RawComparator> theClass = getClass(
     JobContext.KEY_COMPARATOR, null, RawComparator.class);
 // 参数: mapreduce.job.output.key.comparator.class  默认没有配置

     if (theClass != null)
        return ReflectionUtils.newInstance(theClass, this);  //如果能通过参数获取到,则通过反射创建比较器对象
     
 //如果通过参数获取不到,则获取到在driver中设置的map的输出的key的类型,
 // 并判断key的类型是否属于 ritableComparable类型
 // 再尝试为key获取比较器对象.
     return WritableComparator.get(getMapOutputKeyClass().asSubclass(WritableComparable.class), this);
  }

   public static WritableComparator get(
      Class<? extends WritableComparable> c, Configuration conf) {
   // 从comparators中尝试获取key的比较器对象
   // 如果key是我们自己定义的类型,则获取不到
   // 如果key是hadoop的序列化类型,例如 Text, Intwriable等,则能获取到。
       WritableComparator comparator = comparators.get(c);
if (comparator == null) {
  // force the static initializers to run
  forceInit(c);  //强制进行类加载
  // look to see if it is defined now
  comparator = comparators.get(c);   // 再次进行获取
  // if not, use the generic one
  if (comparator == null) { 
  // 如果还获取不到,则直接new一个对象出来。
     comparator = new WritableComparator(c, conf, true);
  }
}
// Newly passed Configuration objects should be used.
   ReflectionUtils.setConf(comparator, conf);
   return comparator;

}

总结:  Hadoop自身的序列化类型,在类加载时, 会把类型 及 对应的比较器对象 注册到
       WritableCompartor中的comparators 这个Map中
   
   如果key是我们自己定义的类型,则我们必须要为该类型提交比较器对象.

   不管是hadoop自身的序列化类型还是我们自己定义的类型,只要作为key来使用,
   则必须要有对应的比较器对象才能够进行比较,才能实现排序.

2.6 比较器 WritableCompartor

  1. public int compare(byte[] b1, int s1, int l1, byte[] b2, int s2, int l2): 底层的实现,我们不动,会调用第2个方法
  2. public int compare(WritableComparable a, WritableComparable b): 我们可以重写,实现比较规则
  3. public int compare(Object a, Object b): 还是会调用到第2个方法

总结: 作为key来使用的类型,需要提供比较器对象, 还要求 key的类必须是WritableComparable类型.

2.7 比较接口 WritableComparable

  1. 要求所有作为key来使用的类型,都需要实现WritableComparable接口.
  2. 该接口中可以通过compareTo定义比较规则.

2.8 排序大总结***********:

   1) 一个接口和一个类
     接口:  WritableComparable :  定义默认的比较规则
     类:    WritableCompartor  :  一般用于定义临时比较规则
  
   2) Hadoop的排序,都是对key的排序,排序时需要比较,比较的时候都是用key类型对应的比较器对象来进行比较.

   3) 对于key的类型来说,如果提供了对应的比较器对象,则使用我们自己提供的,
      如果没有提供对应的比较器对象,则Hadoop会帮我们创建一个比较器对象.
   
   4) hadoop对key的比较,默认调用的是比较器类中的compare(WritableComparable ,WritableComparable)方法.
      第一种情况: 如果自定义的比较器中,重写了compare方法,则使用重写后的方法进行比较.
      第二种情况: 如果自定义的比较器中,没有重写compare方法,则使用的是WritableCompartor的compare(WritableComparable ,WritableComparable)方法,
               而在此方法中,默认的实现是 a. compareTo(b), 因此会调用到key的类中的compareTo方法进行比较.
      第三种情况: 如果没有提供比较器对象,则hadoop默认帮我们创建一个比较器对象, 但是hadoop默认创建的比较器对象
               还是使用WritableCompartor的compare(WritableComparable ,WritableComparable)方法,而该方法中
	       默认的实现a. compareTo(b), 所以最终还是调用到key的类中的compareTo方法进行比较.
   5) 实际使用:
      按照hadoop的设计来说,我们需要提供比较器对象WritableComparator,需要实现WritableComparable.
  按照我们实际使用(偷懒), 不用提交比较器对象,直接实现WritableComparable接口,重写compareTo即可.
  一般来讲, WritableComparable是用来定义默认的比较规则的, WritableComparator用来定义临时的比较规则的.

2.9 全排序和区内排序

全排序就是将排序的规则定义好,只有一个分区一个reduce,数据整体有序
区内排序就是在全排序的基础之上,加上自定义分区即可.

2.10 分组排序

1)      
数据在进入到reduce方法之前,一定要保证数据是有序的,才可以进行所谓的分组. 最终的效果
就是要保证相同key的多个kv对进入到一个reduce方法。

2) hadoop 是如何进行分组比较的?
  
  在ReduceTask中的run方法中 :
  RawComparator comparator = job.getOutputValueGroupingComparator();

  public RawComparator getOutputValueGroupingComparator() {
          Class<? extends RawComparator> theClass = getClass(
           JobContext.GROUP_COMPARATOR_CLASS, null, RawComparator.class);
       //配置项:  mapreduce.job.output.group.comparator.class
               
          if (theClass == null) {
            return getOutputKeyComparator();
          }

        return ReflectionUtils.newInstance(theClass, this);
      }
      
  public RawComparator getOutputKeyComparator() {
          Class<? extends RawComparator> theClass = getClass(
          JobContext.KEY_COMPARATOR, null, RawComparator.class);
        配置项:  mapreduce.job.output.key.comparator.class
          if (theClass != null)
             return ReflectionUtils.newInstance(theClass, this);
            return WritableComparator.get(getMapOutputKeyClass().asSubclass(WritableComparable.class), this);
      }


    3) 真相: 
   
   在分组比较时, hadoop会获取当前key的类型对应的分组比较器对象,如果获取不到,则
   尝试获取当前key的类型对应的排序比较器对象. 如果还获取不到,则hadoop会创建一个
   比较器对象,最终调用到key的类中的compareTo方法.

2.11 所谓的分组,具体怎么实现的?

1) 分组是真正的把所有的数据按照key提前都分成一组一组的吗? 答: 不是.

2) 真正的分组是:
      首先要求数据必须是有序的. 每次使用当前正在处理的数据与下一条数据进行比较,
      得到两个结论: 是否还有下一条数据 以及 下一条数据与当前数据是否为一个组.

2.12 Reducer类中的reduce方法中的key 和 values的设计:

  key 和 value 都是一个变量, 指向了内存中的一个对象,
  每次进行迭代的时候 key  和 value的变量值不变.变的是
  变量指向的堆中对象的内容.

3. Combiner

  1. combiner的工作位置:
    kv从缓冲区中溢写到磁盘时可以使用combiner(只要设置,无条件使用)
    每个MapTask的所有数据都从缓冲区写到磁盘后,在进行归并的时候可以使用combiner(满足条件使用,溢写次数>=3)

  2. Combiner: 合并
    目的就是在每个MapTask中将输出的kv提前进行合并。
    能够降低map到reduce传输的数量及 reduce最终处理的数据量.

  3. Combiner使用限制:
    在不改变业务逻辑的情况下才能使用combiner.

重点:

  1. 分区:
    分区案例
    自定义分区器

  2. 排序:
    完成课堂的排序案例
    理解排序的原理及相关的代码

  3. combiner
    测试combiner的使用

  4. 面向对象知识点
    类 对象 继承 重载 重写

猜你喜欢

转载自blog.csdn.net/qq_43206800/article/details/107525031
今日推荐