Spark 實現分組topn排序 (scala版本)

四種方法實現分組排序

數據集格式:

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)
    }
  }



}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章