在spark中很多时候回去对RDD进行排序,但是官方给的排序规则无法满足我们的需求,许多时候需要我们重新定义排序规则,接下来我们来谈论一下RDD的排序规则。

首先我们通过代码来看一下sparkAPI中自带排序算子sortBy和sortByKey

val conf = new SparkConf().setAppName("sortByKey").setMaster("local[2]")
    val sc =  new SparkContext(conf)
    //模拟数据(学号,数学成绩,语文成绩,英语成绩)
    val gradeRdd =  sc.parallelize(List(("004",90,70,96),("002",87,76,89),("001",90,56,87),("003",82,78,76)))
    //将rdd中的数据转为key-value的形式,使用sortByKey进行排序
    val arrStudent = gradeRdd.map(s => (s._1,s)).sortByKey().values.collect()
    //打印数据
    println(arrStudent.toBuffer)

输出结果是

ArrayBuffer((001,90,56,87), (002,87,76,89), (003,82,78,76), (004,90,70,96))

从上面简单的案例中可以看出sortByKey是对rdd中的pairRDD进行排序的,下面看一下sortByKey的源码

/**
   * Sort the RDD by key, so that each partition contains a sorted range of the elements. Calling
   * `collect` or `save` on the resulting RDD will return or output an ordered list of records
   * (in the `save` case, they will be written to multiple `part-X` files in the filesystem, in
   * order of the keys).
   */
  // TODO: this currently doesn't work on P other than Tuple2!
  def sortByKey(ascending: Boolean = true, numPartitions: Int = self.partitions.length)
      : RDD[(K, V)] = self.withScope
  {
    val part = new RangePartitioner(numPartitions, self, ascending)
    new ShuffledRDD[K, V, V](self, part)
      .setKeyOrdering(if (ascending) ordering else ordering.reverse)
  }

该函数最多可以传两个参数:   

第一个参数 是ascending,参数决定排序后RDD中的元素是升序还是降序,默认是true,也就是升序;

   第二个参数 是numPartitions,该参数决定排序后的RDD的分区个数,默认排序后的分区个数和排序之前的个数相等,即为

this.partitions.size

从代码中可以看出,sortByKey对rdd进行 按范围进行了重新分区,因为对于非排序类型的算子来讲,分区采用的是散列算法分区的,只需要保证相同的key被分配到相同的partition中就可以,并不会影响其他计算操作。

但是对于排序来说,把相同的key分配到相同的partition中还不够,因为最后还要合并所有的partition进行排序合并,如果发生在Driver端,将是一件可怕的事情,所以在进行排序时进行了重分区策略,使排序相近的key分配到同一个Range上,一个partition管理一个Range,方便数据的计算。

温馨提示:sortByKey对单个元素排序很简单,对于多个元素(x1,x2,x3.....),首先会按照x1排序,如果x1相同,则按照x2排序,以此类推。

其实sortByKey是OrderedRDDFunctions类的方法,那么RDD为什么可以使用,在RDD源码中可以看到RDD中利用隐士转换来调用sortByKey

implicit def rddToOrderedRDDFunctions[K : Ordering : ClassTag, V: ClassTag](rdd: RDD[(K, V)])
    : OrderedRDDFunctions[K, V, (K, V)] = {
    new OrderedRDDFunctions[K, V, (K, V)](rdd)
  }

下面看一下sortBy

val conf = new SparkConf().setAppName("sortByKey").setMaster("local[2]")
    val sc =  new SparkContext(conf)
    //模拟数据(学号,数学成绩,语文成绩,英语成绩)
    val gradeRdd =  sc.parallelize(List(("004",90,70,96),("002",87,76,89),("001",90,56,87),("003",82,78,76)))
    //将rdd中的数据转为key-value的形式,使用sortByKey进行排序
    val arrStudent = gradeRdd.sortBy(s=>s._1).collect()
    //打印数据
    println(arrStudent.toBuffer)

输出结果是

ArrayBuffer((001,90,56,87), (002,87,76,89), (003,82,78,76), (004,90,70,96))

可以看出sortBy好像比sortByKey更强大,可以自己随意指定排序字段,接下来看一下sortBy的源码

/**
   * Return this RDD sorted by the given key function.
   */
  def sortBy[K](
      f: (T) => K,
      ascending: Boolean = true,
      numPartitions: Int = this.partitions.length)
      (implicit ord: Ordering[K], ctag: ClassTag[K]): RDD[T] = withScope {
    this.keyBy[K](f)
        .sortByKey(ascending, numPartitions)
        .values
  }

该函数最多可以传三个参数:

第一个参数 是一个函数,该函数的也有一个带T泛型的参数,返回类型和RDD中元素的类型是一致的;   

第二个参数 是ascending,该参数决定排序后RDD中的元素是升序还是降序,默认是true,也就是升序;   

第三个参数 是numPartitions,该参数决定排序后的RDD的分区个数,默认排序后的分区个数和排序之前的个数相等,即为

this.partitions.size

  从sortBy函数的实现可以看出,第一个参数是必须传入的,而后面的两个参数可以不传入。而soartBy内部调用了sortByKey的方法

/**
* Creates tuples of the elements in this RDD by applying `f`.
*/
def keyBy[K](f: T => K): RDD[(K, T)] = {
    map(x => (f(x), x))
}

从keyBy方法中可以看出,该方法是将rdd中的数据按照第一个参数规则将RDD转为key-value的形式,然后调用sortBykey。除此之外可以发现sortBy的第一个参数可以对排序规则进行重新,那么sortByKey怎么重新排序规则楠。

在OrderedRDDFunctions类中可以发现有个变量ordering它是隐形的:private val ordering = implicitly[Ordering[K]]。这个就是默认的排序规则,我们只需要对其进行排序就可以实现自定义排序规则。

val conf = new SparkConf().setAppName("sortByKey").setMaster("local[2]")
    val sc =  new SparkContext(conf)
    //模拟数据(学号,姓名)
    val studentRdd =  sc.parallelize(List((3,"xiaoming"),(12,"xiaohong"),(1,"xiaogang"),(23,"xiaoliu")))
    println(studentRdd.collect().toBuffer)
    implicit val sortByGrade = new Ordering[Int]{
       override def compare(x: Int, y: Int) = {
          x.toString.compareTo(y.toString)
       }
    }
    val sortStudent = studentRdd.sortByKey()
    println(sortStudent.collect().toBuffer)

输出结果是

排序前数据:ArrayBuffer((3,xiaoming), (12,xiaohong), (1,xiaogang), (23,xiaoliu))
排序后数据:ArrayBuffer((1,xiaogang), (12,xiaohong), (23,xiaoliu), (3,xiaoming))

默认是按照Int数据进行排序的,修改排序规则为字符串排序。 

下面二种是对sort自定排序规则

val conf = new SparkConf().setAppName("gradeSoet").setMaster("local[2]")
    val sc =  new SparkContext(conf)
    //学号,数学成绩,语文成绩,英语成绩
   val gradeRdd =  sc.parallelize(List(("001",90,70,96),("002",87,76,89),("003",90,56,87),("004",82,78,76)))
import OrderContext._  //隐式转换
   val sortRdd =  gradeRdd.sortBy(x=>Student(x._2,x._3),false)
    println(gradeRdd.collect().toBuffer)
    println(sortRdd.collect().toBuffer)

输出结果

ArrayBuffer((001,90,70,96), (002,87,76,89), (003,90,56,87), (004,82,78,76))
ArrayBuffer((003,90,56,87), (001,90,70,96), (002,87,76,89), (004,82,78,76))

排序规则是先按照数数学成绩排序,然后在按照语文成绩排序

第一种方法:自定义case class类来实现排序

case class Student(math:Int,chinese:Int) extends Ordered[Student] with Serializable{
    override def compare(that: Student): Int = {
      if(this.math == that.math){
        this.chinese - that.chinese
      }else{
        this.math - that.math
      }
    }
  }

第二种使用隐式转换的方式,在代码中导入隐式转换类

case class Student(math:Int,chinese:Int) extends Serializable
object OrderContext{
 // implicit object StudentOrdering extends Ordering[Student]{
  implicit val gradeOrdering = new Ordering[Student]{
    override def compare(x: Student, y: Student): Int = {
      if(x.math > y .math) 1
      else if(x.math == y.math){
        if(x.chinese > y.chinese) -1 else 1
      }else -1
    }
  }
}