SparkにはrepartitionAndSortWithinPartitions演算子が用意されています。最初に、この演算子の有用性について説明します。
オペレーターは、指定されたパーティショナーごとにグループ化し、グループ内でソートできます。
したがって、次のようにしてニーズを満たすことができます。
たとえば、次のとおりです。
例1. rddデータ内の同じクラスの生徒をパーティションに分割し、スコアの降順でソートする
例2.同じ組み合わせKeyが同じパーティションにグループ化され、パーティションは最初にKEYでソートされ、同じキーが他のキーでソートされます
まずは公式サイトから機能紹介をご覧ください:
アドレス:http : //spark.apache.org/docs/latest/rdd-programming-guide.html#working-with-key-value-pairs
repartitionAndSortWithinPartitions(パーティション) | 指定されたパーティショナーに従ってRDDを再パーティション化し、結果の各パーティション内で、レコードをキーでソートします。これは、repartition 各パーティション内で呼び出してから並べ替えるよりも効率的です。これは 、並べ替えをシャッフル機構に押し下げることができるためです。 |
repartitionAndSortWithinPartitionsは、主に同じKEYの要素を、指定されたパーティショナーを介して指定されたパーティションに送信し、KEYに従ってソートすることがわかります。ヒント:カスタムの並べ替えルールに従って2次並べ替えを実行できます。
さらに、repartitionAndSortWithinPartitionsは効率的な演算子であり、最初にrepartitionを呼び出してからグループ内で並べ替えるよりも効率的です。これは、並べ替えがシャッフルと並べ替えの間にシャッフルプロセス中に実行されるためです。詳細はSpark shuffleをご覧ください 。読み出し動作。
ソースコードを大まかに見てみましょう:
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)
}
/**
* Repartition the RDD according to the given partitioner and, within each resulting partition,
* sort records by their keys.
*
* This is more efficient than calling `repartition` and then sorting within each partition
* because it can push the sorting down into the shuffle machinery.
*/
def repartitionAndSortWithinPartitions(partitioner: Partitioner): RDD[(K, V)] = self.withScope {
new ShuffledRDD[K, V, V](self, partitioner).setKeyOrdering(ordering)
}
特定の要件でこの演算子を使用する方法を見てみましょう。
例1. rddデータ内の同じクラスの生徒をパーティションに分割し、スコアの降順でソートする
実装コード
package com.gaosi.spark.demo
/**
* Created by szh on 2019/9/19.
*/
import org.apache.spark.{SparkConf, SparkContext}
class Student {
}
//创建key类,key组合键为grade,score
case class StudentKey(grade: String, score: Int)
// extends Ordered[StudentKey] {
// def compare(that: StudentKey): Int = {
// var result: Int = this.grade.compareTo(that.grade)
// if (result == 0) {
// result = that.score.compareTo(this.score)
// }
// result
// }
//}
object StudentKey {
implicit def orderingByGradeStudentScore[A <: StudentKey]: Ordering[A] = {
Ordering.by(fk => (fk.grade, fk.score * -1))
}
}
//创建分区类
import org.apache.spark.Partitioner
class StudentPartitioner(partitions: Int) extends Partitioner {
require(partitions >= 0, s"Number of partitions ($partitions) cannot be negative.")
override def numPartitions: Int = partitions
override def getPartition(key: Any): Int = {
val k = key.asInstanceOf[StudentKey]
Math.abs(k.grade.hashCode()) % numPartitions
}
}
object Student {
def main(args: Array[String]) {
//定义hdfs文件索引值
val grade_idx: Int = 0
val student_idx: Int = 1
val course_idx: Int = 2
val score_idx: Int = 3
//定义转化函数,不能转化为Int类型的,给默认值0
def safeInt(s: String): Int = try {
s.toInt
} catch {
case _: Throwable => 0
}
//定义提取key的函数
def createKey(data: Array[String]): StudentKey = {
StudentKey(data(grade_idx), safeInt(data(score_idx)))
}
//定义提取value的函数
def listData(data: Array[String]): List[String] = {
List(data(grade_idx), data(student_idx), data(course_idx), data(score_idx))
}
def createKeyValueTuple(data: Array[String]): (StudentKey, List[String]) = {
(createKey(data), listData(data))
}
//设置master为local,用来进行本地调试
val conf = new SparkConf().setAppName("Student_partition_sort").setMaster("local")
val sc = new SparkContext(conf)
//学生信息是打乱的
val student_array = Array(
"c001,n003,chinese,59",
"c002,n004,english,79",
"c002,n004,chinese,13",
"c001,n001,english,88",
"c001,n002,chinese,10",
"c002,n006,chinese,29",
"c001,n001,chinese,54",
"c001,n002,english,32",
"c001,n003,english,43",
"c002,n005,english,80",
"c002,n005,chinese,48",
"c002,n006,english,69"
)
//将学生信息并行化为rdd
val student_rdd = sc.parallelize(student_array)
//生成key-value格式的rdd
val student_rdd2 = student_rdd.map(line => line.split(",")).map(createKeyValueTuple)
//根据StudentKey中的grade进行分区,并根据score降序排列
val student_rdd3 = student_rdd2.repartitionAndSortWithinPartitions(new StudentPartitioner(10))
//打印数据
student_rdd3.collect.foreach(println)
}
}
アウトプット
(StudentKey(c001,88)、List(c001、n001、english、88))
(StudentKey(c001,59)、List(c001、n003、chinese、59))
(StudentKey(c001,54)、List(c001、 n001、chinese、54))
(StudentKey(c001,43)、List(c001、n003、english、43))
(StudentKey(c001,32)、List(c001、n002、english、32))
(StudentKey(c001、 10)、List(c001、n002、chinese、10))
(StudentKey(c002,80)、List(c002、n005、english、80))
(StudentKey(c002,79)、List(c002、n004、english、79 ))
(StudentKey(c002,69)、List(c002、n006、english、69)))
(StudentKey(c002,48)、List(c002、n005、chinese、48))
(StudentKey(c002,29)、List( c002、n006、chinese、29))
(StudentKey(c002,13)、List(c002、n004、chinese、13))
ここでは2つの点に注意します。
一つはパーティショナーです
import org.apache.spark.Partitioner
class StudentPartitioner(partitions: Int) extends Partitioner {
require(partitions >= 0, s"Number of partitions ($partitions) cannot be negative.")
override def numPartitions: Int = partitions
override def getPartition(key: Any): Int = {
val k = key.asInstanceOf[StudentKey]
Math.abs(k.grade.hashCode()) % numPartitions
}
}
hashCodeは負の値になる可能性があるので、Math.absを呼び出してください。
2番目のポイントは、並べ替えの実装です
object StudentKey {
implicit def orderingByGradeStudentScore[A <: StudentKey]: Ordering[A] = {
Ordering.by(fk => (fk.grade, fk.score * -1))
}
}