图解Spark排序算子sortBy的核心源码

一、案例说明

val money = ss.sparkContext.parallelize(
List(("Alice", 9973),
("Bob", 6084),
("Charlie", 3160),
("David", 8588),
("Emma", 8241),
("Frank", 117),
("Grace", 5217),
("Hannah", 5811),
("Ivy", 4355),
("Jack", 2106))
)
money.sortBy(x =>x._2, false).foreach(println)

(Ivy,4355)
(Grace,5217)
(Jack,2106)
(Frank,117)
(Emma,8241)
(Alice,9973)
(Charlie,3160)
(Bob,6084)
(Hannah,5811)
(David,8588)


money.sortBy(x =>x._2, false).collect().foreach(println)

money.repartition(1).sortBy(x =>x._2, false).foreach(println)

money.sortBy(x =>x._2, false).saveAsTextFile("result")

(Alice,9973)
(David,8588)
(Emma,8241)
(Bob,6084)
(Hannah,5811)
(Grace,5217)
(Ivy,4355)
(Charlie,3160)
(Jack,2106)
(Frank,117)


二、sortBy源码分析

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
}


2.1、逐节分析sortBy源码之一：this.keyByK

this.keyBy[K](f)这行代码是基于_.sortBy(x =>x._2, false)传进来的x =>x._2重新生成一个新RDD数据，可以进入到其底层源码看一下——

def keyBy[K](f: T => K): RDD[(K, T)] = withScope {
val cleanedF = sc.clean(f)
map(x => (cleanedF(x), x))
}


map(x => (sc.clean(x =>x._2), x))


sc.clean(x =>x._2)这个clean相当是对传入的函数做序列化，因为最后会将这个函数得到结果当作排序key分发到不同分区节点做排序，故而涉及到网络传输，因此做序列化后就方便在分布式计算中在不同节点之间传递和执行函数，clean最终底层实现是这行代码SparkEnv.get.closureSerializer.newInstance().serialize(func)，感兴趣可以深入研究。

keyBy最终会生成一个新的RDD，至于这个结构是怎样的，通过原先的测试数据调用keyBy打印一下就一目了然——

val money = ss.sparkContext.parallelize(
List(("Alice", 9973),
("Bob", 6084),
("Charlie", 3160),
("David", 8588),
("Emma", 8241),
("Frank", 117),
("Grace", 5217),
("Hannah", 5811),
("Ivy", 4355),
("Jack", 2106))
)
money.keyBy(x =>x._2).foreach(println)

(5217,(Grace,5217))
(5811,(Hannah,5811))
(8588,(David,8588))
(8241,(Emma,8241))
(9973,(Alice,9973))
(3160,(Charlie,3160))
(4355,(Ivy,4355))
(2106,(Jack,2106))
(117,(Frank,117))
(6084,(Bob,6084))


2.2、逐节分析sortBy源码之二：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).
*
*按键对RDD进行排序，以便每个分区包含一个已排序的元素范围。
在结果RDD上调用collect或save将返回或输出一个有序的记录列表
(在save情况下，它们将按照键的顺序写入文件系统中的多个part-X文件)。
*/
// 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)
}


money.sortBy(x => x._2, false).foreachPartition(x => {
//val index = UUID.randomUUID()
x.foreach(x => {
println("分区号" + partitionId + "：   " + x)
})
})



sortBy主要流程如下，假设运行环境有3个分区，读取的数据去创建一个RDD的时候，会按照默认Hash分区器将数据分到3个分区里。

shuffleRDD中，使用mapPartitions会对每个分区的数据按照key进行相应的升序或者降序排序，得到分区内有序的结果集。

2.3、逐节分析sortBy源码之三：.values

sortBy底层源码里 this.keyBy[K](f).sortByKey(ascending, numPartitions).values，在sortByKey之后，最后调用了.values。源码.values里面是def values: RDD[V] = self.map(_._2)，就意味着，排序完成后，只返回x._2的数据，用于排序生成的RDD。类似排序过程中RDD是(5217,(Grace,5217))这样结构，排序后，若只返回x._2，就只返回(Grace,5217)这样结构的RDD即可。

三、合并各个分区的排序，返回全局排序

posted @ 2023-09-18 22:38  朱季谦  阅读(65)  评论(2编辑  收藏  举报