四種方法實現分組排序
數據集格式:
http://bigdata.edu360.cn/laoduan
http://bigdata.edu360.cn/laoduan
http://javaee.edu360.cn/xiaoxu
http://javaee.edu360.cn/xiaoxu
http://javaee.edu360.cn/laoyang
http://javaee.edu360.cn/laoyang
http://javaee.edu360.cn/laoyang
按照每個學科求老師訪問量排序結果
方案1:先reduceByKey進行聚合,再按照學科進行分組,在每個分組內進行排序
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.rdd.RDD
/**
* 求每個學科的最受歡迎的老師方法1
*
* 先reduceByKey進行聚合,再按照學科進行分組,在分組內進行排序
*
* 缺點:分組內數據用迭代器調用,當數據量較大時,迭代器的數據放到內存中會內存溢出
* (分組內數據進行排序,調用的是集合上的排序方法,不是RDD的,集合上的數據需要一次性加載到內存中)
*/
object GroupFavoriteTeacher1 {
def main(args: Array[String]): Unit = {
val favTeacher: SparkConf = new SparkConf().setMaster("local[2]").setAppName("FavTeacher")
val context: SparkContext = new SparkContext(favTeacher)
// 讀取一行
// 數據格式: http://bigdata.edu360.cn/laozhang
val lines: RDD[String] = context.textFile("./teacher.log")
// 切分形成((學科,老師),1)
val sbjectTeacher: RDD[((String, String), Int)] = lines.map(line => {
val strings: Array[String] = line.split("/")
val sbject: String = strings(2).split("\\.")(0) // 學科
val teacher = strings(3) // 老師
((sbject, teacher), 1)
})
// 聚合相成 ((學科,老師), n), 相同(學科,老師)僅保留一條記錄
val groupTeahcerReduce: RDD[((String, String), Int)] = sbjectTeacher.reduceByKey((a, b) => {
a + b
})
// 按照學科進行分組,【並指定分組後RDD的分區數量】 分組條件相同的數據會在一個分組中,一個分區內可以有多個分組
// ------經過分組後,相同學科的數據(一個分組內的數據)可以用一個迭代器調用
val group: RDD[(String, Iterable[((String, String), Int)])] = groupTeahcerReduce.groupBy(
(t:((String, String), Int))=> t._1._1,2)
// 將每個組內的數據的迭代器拿出(一個迭代器對應一個分組),按照n進行排序
val value: RDD[(String, List[((String, String), Int)])] = group.mapValues((it) => {
it.toList.sortBy(u => {
u._2
}).reverse
})
// action算子進行驅動
// value.foreach(u=>println(u._1 +":"+u._2))
value.foreach(u=>{
print(u._1+":")
u._2.foreach(it=>{print(it._1._2+"?"+it._2)})
println()
})
context.stop()
}
}
方案2:按照學科進行過濾,每次對一個學科的數據進行排序
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.rdd.RDD
/**
* 求每個學科的最受歡迎的老師方法2
*
* 按照學科進行過濾,每次過濾出一個學科的老師放入一個RDD中,再對過濾出的RDD進行排序,
* RDD排序方法是內存+磁盤的
*
* 缺點:需要多次過濾,效率低
*/
object GroupFavoriteTeacher2 {
def main(args: Array[String]): Unit = {
val favTeacher: SparkConf = new SparkConf().setMaster("local[2]").setAppName("FavTeacher")
val context: SparkContext = new SparkContext(favTeacher)
// 讀取一行
val lines: RDD[String] = context.textFile("./teacher.log") // http://bigdata.edu360.cn/laozhang
// 切分形成((學科,老師),1)
val sbjectTeacher: RDD[((String, String), Int)] = lines.map(line => {
val strings: Array[String] = line.split("/")
val sbject: String = strings(2).split("\\.")(0) // 學科
val teacher = strings(3) // 老師
((sbject, teacher), 1)
})
// 分組聚合 ((學科,老師),n)
val reduced: RDD[((String, String), Int)] = sbjectTeacher.reduceByKey((a, b) => {
a + b
})
// 獲取所有的學科集合
val keys: RDD[(String, String)] = reduced.keys
val subjects: RDD[String] = keys.map(u => {
u._1
})
// 這裏需要用一個action算子驅動subjects,並放到集合中,遍歷集合中數據
// 因爲不能在一個RDD上的transformation裏面調用其他的transformation
val strings: Array[String] = subjects.collect()
// 遍歷每個學科,一次過濾出一個學科的所有老師進行排序
for(sb<-strings){
// 過濾出的數據放到一個RDD中,該RDD中的數據學科相同
val filtered: RDD[((String, String), Int)] = reduced.filter(it => {
it._1._1==sb
})
// 將每次過濾出的相同學科的數據進行排序
// RDD排序,內存加磁盤進行排序
val sorted: RDD[((String, String), Int)] = filtered.sortBy(it => {
it._2
}, false)
// action算子進行驅動
sorted.foreach(u=>{println(u)})
}
context.stop()
}
}
方案3:自定義分區器,將相同學科的數據放在同一個分區內,再對每個分區進行排序
import org.apache.spark.{Partitioner, SparkConf, SparkContext}
import org.apache.spark.rdd.RDD
import scala.collection.mutable
/**
* 求每個學科的最受歡迎的老師方法3
*
* 自定義分區器實現分組聚合,先按照(key,vale):((學科,老師),1)進行聚合
* 然後按照學科進行分區,將相同學科的數據放在同一分區。然後針對每個分區中的數據按照n進行排序
*
*缺點:集合上進行排序,會內存溢出;
* shuffle次數較多,速度慢, reduceByKey、partitionBy需要shuffle
*/
object GroupFavoriteTeacher3 {
def main(args: Array[String]): Unit = {
val favTeacher: SparkConf = new SparkConf().setMaster("local[3]").setAppName("FavTeacher")
val context: SparkContext = new SparkContext(favTeacher)
// 讀取一行
val lines: RDD[String] = context.textFile("./teacher.log") // http://bigdata.edu360.cn/laozhang
// 切分形成((學科,老師),1)
val sbjectTeacher: RDD[((String, String), Int)] = lines.map(line => {
val strings: Array[String] = line.split("/")
val sbject: String = strings(2).split("\\.")(0) // 學科
val teacher = strings(3) // 老師
((sbject, teacher), 1)
})
// 分組聚合 ((學科,老師),n)
val reduced: RDD[((String, String), Int)] = sbjectTeacher.reduceByKey((a, b) => {
a + b
})
// 獲取所有的學科集合
val keys: RDD[(String, String)] = reduced.keys
val subjects: RDD[String] = keys.map(u => {
u._1
})
// 這裏需要用一個action算子驅動subjects,,並放到集合中,遍歷集合中數據
// 因爲不能在一個RDD上的transformation裏面調用其他的transformation
val strings: Array[String] = subjects.collect()
// 自定義一個分區器,按照學科進行分區
val sbPartitioner: SubjectPartitioner = new SubjectPartitioner(strings)
val paritioner: RDD[((String, String), Int)] = reduced.partitionBy(sbPartitioner)
// 在每個分區內排序,調用的是集合上的排序,
// mapPartitions算子,每次拿一個分區中數據,(用迭代器獲取)
val sorted: RDD[((String, String), Int)] = paritioner.mapPartitions((it) => {
it.toList.sortBy(u => {
u._2
}).reverse.iterator
})
// action算子驅動
sorted.foreach(u=>{println(u)})
context.stop()
}
/**
* 自定義分區器,按照學科進行分區
*/
class SubjectPartitioner( subjects :Array[String]) extends Partitioner{
// 設置分區規則,設置每個學科對應的分區ID,用map存。
// 不能用subject.hashMap % numPartitions,這樣不能保證每個分區內只有一個學科的數據
val rules: mutable.HashMap[String, Int] = new mutable.HashMap[String, Int]()
var i = 0;
for(sb <- subjects){
rules.put(sb, i) // rules(sb) = i
i = i+1
}
// 返回分區的數量(下一個RDD有多少個分區)
override def numPartitions: Int = subjects.length
// 根據傳入的key計算該key所在的分區編號
override def getPartition(key: Any): Int ={
val subject: String = key.asInstanceOf[(String, String)]._1 //強轉格式
rules.getOrElse(subject,0)
}
}
}
方案4:在ReduceByKey的同時調用自定義分區器,減少shuffle次數,提高效率
import org.apache.spark.{Partitioner, SparkConf, SparkContext}
import org.apache.spark.rdd.RDD
import scala.collection.mutable
/**
*求每個學科的最受歡迎的老師方法4
*
* 在reduceByKey的同時按照學科進行分區,減少shuffle的次數
*
* 缺點:集合上進行排序,會內存溢出
*/
object GroupFavoriteTeacher4 {
def main(args: Array[String]): Unit = {
val favTeacher: SparkConf = new SparkConf().setMaster("local[3]").setAppName("FavTeacher")
val context: SparkContext = new SparkContext(favTeacher)
// 讀取一行
val lines: RDD[String] = context.textFile("./teacher.log") // http://bigdata.edu360.cn/laozhang
// 切分形成((學科,老師),1)
val sbjectTeacher: RDD[((String, String), Int)] = lines.map(line => {
val strings: Array[String] = line.split("/")
val sbject: String = strings(2).split("\\.")(0) // 學科
val teacher = strings(3) // 老師
((sbject, teacher), 1)
})
// 獲取所有學科的集合
val subjects: Array[String] = sbjectTeacher.map(u => {
u._1._1
}).distinct().collect()
// reduceByKey的同時按照指定分區器進行分區
// 該RDD的一個分區內僅有一個學科的數據
val partitioner: SubjectPartitioner = new SubjectPartitioner(subjects)
val reduced: RDD[((String, String), Int)] = sbjectTeacher.reduceByKey(partitioner, (a, b) => {
a + b
})
// 在每個分區內排序,調用的是集合上的排序,
// mapPartitions算子,每次拿一個分區中數據,(用迭代器獲取)
val sorted: RDD[((String, String), Int)] = reduced.mapPartitions(it => {
it.toList.sortBy(u => {
u._2
}).reverse.iterator
})
// action算子
sorted.foreach(u=>{println(u)})
context.stop()
}
/**
* 自定義分區器,按照學科進行分區
*/
class SubjectPartitioner( subjects :Array[String]) extends Partitioner{
// 設置分區規則,設置每個學科對應的分區ID,用map存。
// 不能用subject.hashMap % numPartitions,這樣不能保證每個分區內只有一個學科的數據
val rules: mutable.HashMap[String, Int] = new mutable.HashMap[String, Int]()
var i = 0;
for(sb <- subjects){
rules.put(sb, i) // rules(sb) = i
i = i+1
}
// 返回分區的數量(下一個RDD有多少個分區)
override def numPartitions: Int = subjects.length
// 根據傳入的key計算該key所在的分區編號
override def getPartition(key: Any): Int ={
val subject: String = key.asInstanceOf[(String, String)]._1 //強轉格式
rules.getOrElse(subject,0)
}
}
}