c++ map 自定义排序_Spark实现分组Top-k排序的四种方案(scala语言)

Spark中实现分组排序(取Top-k)的四种方法。

以求每个学科最受欢迎的老师为例,假设学科下每个老师的主页访问量的多少代表该老师的受欢迎程度。

截取log日志文件中的网址数据的格式为:

b6f5575917ebd66aa289f20288ced046.png

数据格式:http://学科.edu360.cn/老师

首先读取文件,切分数据,构造出RDD[((String, String), Int)格式的[(学科,老师),1]的数据

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

方案1:先使用reduceByKey算子,将聚合形成((学科,老师),n),再使用groupby算子按照学科进行分组,然后使用mapValues算子用迭代器获取组内数据,将组内数据放入集合内,再调用集合上的排序方法进行排序。

    // 聚合相成 ((学科,老师), 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.take(3)   })

该方案的缺点:分组内数据进行排序,调用的是集合上的排序方法,不是RDD的,集合上的数据需要一次性加载到内存中,当组内数据较大时,可能会内存溢出

方案2:由于学科数量有限,可以将所有学科放入一个集合中,然后对上面的((学科,老师),n)格式的RDD调用filter算子进行过滤,每次过滤出仅包含一个学科的数据,过滤后的数据会存在一个新的RDD中,对新的RDD调用RDD上的sortBy算子,按照n进行排序即可。

    // 获取所有的学科集合    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 {        it._1._1==sb      })      // 将每次过滤出的相同学科的数据进行排序      // RDD排序,内存加磁盘进行排序      val sorted: RDD[((String, String), Int)] = filtered.sortBy(it => {        it._2      }, false)      // action算子进行驱动      sorted.foreach(u=>{println(u)})    }

需要注意的是,存学科的RDD需要用action算子进行驱动一下,用集合表示。因为不能在一个transformation算子里面调用其他的transformation。

优缺点:很明显,需要多次过滤,效率慢。但是这里的排序是RDD上的排序,不用担心内存溢出的问题。

方案3:自定义分区,将相同学科的数据放在同一分区内,每个分区对应一个Task,再针对每个分区进行排序。

首先是自定义的分区器类,按照学科进行分区,这里一定需要保证每个学科对应的数据应该在不同的分区内。分区器的构造参数是学科集合。

 /**   * 自定义分区器,按照学科进行分区   */  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 

排序过程:先生成构造分区器的学科集合,构造自定义分区器。然后调用partitionBy算子对数据进行分区。最后调用mapPartitions算子获取一个分区上的数据,将数据放入集合中进行排序。

     // 获取所有的学科集合    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    })

该方案的缺点:和方案1一样,集合上进行排序,会有可能内存溢出。并且reduceByKey和partitionBy算子都需要shuffle,读写磁盘的操作就比较多。

方案四:自定义分区器,并且减少shuffle次数。在reduceByKey的同时按照学科进行分区,减少shuffle的次数。

使用方案三的自定义分区器。

排序过程:

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

方案四优缺点:和方案1一样,集合上进行排序,会有可能内存溢出。但是相比方案三,会减少shuffle的次数。

另外,您还有什么不同的方案欢迎评论留言,谢谢!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值