spark学习三——spark案例分析 分组TopN

数据:http://bigdata.edu360.cn/laozhang
http://bigdata.edu360.cn/laozhang
http://bigdata.edu360.cn/laozhao
http://bigdata.edu360.cn/laozhao
http://bigdata.edu360.cn/laozhao
http://bigdata.edu360.cn/laozhao
http://bigdata.edu360.cn/laozhao
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
http://bigdata.edu360.cn/laozhao
http://bigdata.edu360.cn/laozhao
http://bigdata.edu360.cn/laozhao
http://bigdata.edu360.cn/laozhao
http://bigdata.edu360.cn/laozhao
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
http://bigdata.edu360.cn/laozhao
http://bigdata.edu360.cn/laozhao
http://bigdata.edu360.cn/laozhao
http://bigdata.edu360.cn/laozhao
http://bigdata.edu360.cn/laozhao
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
http://php.edu360.cn/laoli
http://php.edu360.cn/laoliu
http://php.edu360.cn/laoli
http://php.edu360.cn/laoli

任务一

求出点击次数最多的老师

解析:然后用分词计数思路解决。

  1. 首先将数据进行切分,将url的形式切分成,将学科、老师数据进行切分,然后进行存储,将老师和Int 1进行组合构成元组。

  2. 先按Key(老师)进行聚合

  3. 根据聚合后的value(次数)进行排序
    然后就输出

package cn.edu360.day3

import java.net.URL

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

 
object FavTeacher {

  def main(args: Array[String]): Unit = {

    val conf = new SparkConf().setAppName("FavTeacher").setMaster("local[4]")
    val sc = new SparkContext(conf)

    //指定以后从哪里读取数据
    val lines: RDD[String] = sc.textFile(args(0))
    //整理数据
    val teacherAndOne = lines.map(line => {
      val index = line.lastIndexOf("/")
      val teacher = line.substring(index + 1)
      //val httpHost = line.substring(0, index)
      //val subject = new URL(httpHost).getHost.split("[.]")(0)
      (teacher, 1)
    })
    //聚合
    val reduced: RDD[(String, Int)] = teacherAndOne.reduceByKey(_+_)
    //排序
    val sorted: RDD[(String, Int)] = reduced.sortBy(_._2, false)
    //触发Action执行计算
    val reslut: Array[(String, Int)] = sorted.collect()

    //打印
    println(reslut.toBuffer)

    sc.stop()




  }
}

分组求TopN案例1

在这里插入图片描述

处理过程:

  1. 首先将数据进行切分,将url的形式切分成,将学科、老师数据进行切分,然后进行存储, 构造这样的数据((subject, teacher), 1)
  2. 将(subject, teacher)作为key,对其进行聚合累加:调用方法reduceByKey(+),在此进行了一次shullfe
  3. 然后根据subject进行分组,reduced.groupBy(_._1._1),调用方法返回的是RDD[(String, Iterable[((String, String), Int)])]这种形式,一个构造器中含有的是一个subject的内容,在此进行了一次shullfe
  4. 然后分别其Value,即对每个构造器进行操作,进行排序(排序调用,scala的sortBy方法是在内存上进行操作,当数据量太大的时候容易内存爆了),取TopN 。mapValues(.toList.sortBy(._2).reverse.take(topN))
  5. 触发Action执行计算 val reslut: Array[(String, Int)] = sorted.collect()

数据量少的时候比较快,适合数据量小的情况,但是数据量的时候容易发生数据溢出

package cn.edu360.day3

import java.net.URL

import org.apache.spark.rdd.RDD
import org.apache.spark.{HashPartitioner, SparkConf, SparkContext}


object GroupFavTeacher1 {

  def main(args: Array[String]): Unit = {

    val topN = args(1).toInt

    val conf = new SparkConf().setAppName("FavTeacher").setMaster("local[4]")
    val sc = new SparkContext(conf)

    //指定以后从哪里读取数据,输出数据为((subject, teacher), 1)
    val lines: RDD[String] = sc.textFile(args(0))
    //整理数据
    val sbjectTeacherAndOne: RDD[((String, String), Int)] = lines.map(line => {
      val index = line.lastIndexOf("/")
      val teacher = line.substring(index + 1)
      val httpHost = line.substring(0, index)
      val subject = new URL(httpHost).getHost.split("[.]")(0)
      ((subject, teacher), 1)
    })



    //聚合,将学科和老师联合当做key,进行计数,这里进行了一次shullfe
    val reduced: RDD[((String, String), Int)] = sbjectTeacherAndOne.reduceByKey(_+_)

    //分组排序(按学科进行分组)
    //[学科,该学科对应的老师的数据]
    //返回的结果是一个迭代器,属于同一学科的在一个迭代器当中

    //val grouped: RDD[(String, Iterable[((String, String), Int)])] = reduced.groupBy((t: ((String, String), Int)) =>t._1._1, 4)   也可以指定分区,使得一个分区一个学科

    val grouped: RDD[(String, Iterable[((String, String), Int)])] = reduced.groupBy(_._1._1)

    //经过分组后,一个分区内可能有多个学科的数据,一个学科就是一个迭代器
    //将每一个组拿出来进行操作
    //为什么可以调用sacla的sortby方法呢?因为一个学科的数据已经在一台机器上的一个scala集合里面了,相当于对LIst调用sortBy操作
    //缺点是,scala的sortBy方法是在内存上进行操作,当数据量太大的时候容易内存爆了
    val sorted = grouped.mapValues(_.toList.sortBy(_._2).reverse.take(topN))

    //收集结果
    val r: Array[(String, List[((String, String), Int)])] = sorted.collect()

    //打印
    println(r.toBuffer)

    sc.stop()


  }
}

分组求TopN案例2

过滤多次提交
处理过程:

  1. 首先将数据进行切分,将url的形式切分成,将学科、老师数据进行切分,然后进行存储, 构造这样的数据((subject, teacher), 1)

  2. 将(subject, teacher)作为key,对其进行聚合累加:调用方法reduceByKey(+),在此进行了一次shullfe

  3. //计算有多少学科,获取全部学list,调用reduced.map(_._1._1).distinct().collect()

  4. 利用for循环,遍历每个学科,然后用学科作为条件,运用reduced.filter(_._1._1 == sb)将该学科过滤出来

  5. 每次循环会得到一个rdd,只有一个学科,然后对其调用sortBy(_._2, false).take(topN)(调用得是rdd的方法sortBy,则不会只用内存计算,不用担心内存溢出的问题),然后action触发计算

每次循环触发一次action,每次计算一部分数据,,再加上sortBy方法计算是运用内存+磁盘的方式,计算压力比较小,适合每个学科数据量比较的情况

package cn.edu360.day3

import java.net.URL

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

object GroupFavTeacher2 {

  def main(args: Array[String]): Unit = {

    val topN = args(1).toInt

  

    val conf = new SparkConf().setAppName("GroupFavTeacher2").setMaster("local[4]")
    val sc = new SparkContext(conf)

    //指定以后从哪里读取数据
    val lines: RDD[String] = sc.textFile(args(0))
    //整理数据
    val sbjectTeacherAndOne: RDD[((String, String), Int)] = lines.map(line => {
      val index = line.lastIndexOf("/")
      val teacher = line.substring(index + 1)
      val httpHost = line.substring(0, index)
      val subject = new URL(httpHost).getHost.split("[.]")(0)
      ((subject, teacher), 1)
    })



    //聚合,将学科和老师联合当做key
    val reduced: RDD[((String, String), Int)] = sbjectTeacherAndOne.reduceByKey(_+_)

 //计算有多少学科
    val subjects: Array[String] = reduced.map(_._1._1).distinct().collect()

    //cache到内存
    //val cached = reduced.cache()

    //scala的集合排序是在内存中进行的,但是内存有可能不够用
    //可以调用RDD的sortby方法,内存+磁盘进行排序

    for (sb <- subjects) {
      //该RDD中对应的数据仅有一个学科的数据(因为过滤过了)
      val filtered: RDD[((String, String), Int)] = reduced.filter(_._1._1 == sb)

      //现在调用的是RDD的sortBy方法,(take是一个action,会触发任务提交)
      val favTeacher = filtered.sortBy(_._2, false).take(topN)

      //打印
      println(favTeacher.toBuffer)
    }

    sc.stop()


  }
}

分组求TopN案例3

分区的三种形式

1、HashPartitioner

scala> val counts = sc.parallelize(List((1,'a'),(1,'aa'),(2,'b'),(2,'bb'),(3,'c')), 3)
.partitionBy(new HashPartitioner(3))

HashPartitioner确定分区的方式:partition = key.hashCode () % numPartitions

2、RangePartitioner

scala> val counts = sc.parallelize(List((1,'a'),(1,'aa'),(2,'b'),(2,'bb'),(3,'c')), 3)
.partitionBy(new RangePartitioner(3,counts))

RangePartitioner会对key值进行排序,然后将key值被划分成3份key值集合。

3、CustomPartitioner

CustomPartitioner可以根据自己具体的应用需求,自定义分区。

分区器中含有两个需要重写的方法

  1. override def numPartitions: Int = sbs.length
    可以在这个方法指定分区的数量
  2. override def getPartition(key: Any): Int = {}
    可以在此指定分区数量 ,默认是按照hashcode的规则来指定分区,输入参数任意,输出参数是Int即分区
CustomPartitioner(numParts: Int) extends Partitioner {
 override def numPartitions: Int = numParts
 
 override def getPartition(key: Any): Int =
 {
       if(key==1)){
	0
       } else if (key==2){
       1} else{ 
       2 }
  } 
}
scala> val counts = sc.parallelize(List((1,'a'),(1,'aa'),(2,'b'),(2,'bb'),(3,'c')), 3).partitionBy(new CustomPartitioner(3))

实现步骤:
处理过程:

  1. 首先将数据进行切分,将url的形式切分成,将学科、老师数据进行切分,然后进行存储, 构造这样的数据((subject, teacher), 1)
  2. 将(subject, teacher)作为key,对其进行聚合累加:调用方法reduceByKey(+),在此进行了一次shullfe
  3. 然后调用自定义构造器,在构造器中,构造一个hashmap,将subject与数值存入当中,规则是将构造器中的规则重写将输入subject时,输出它的分区编号
  4. 调用partitionBy(sbPatitioner),将构造的分区器传入,则可以重新制定分区规则,然后就会按分区进行分组,一个学科一个分区
  5. 调用mapPartitions方法,对每个分区进行独立的计算,在此进行了一次shullfe
  6. 每个分区中将迭代器转换成list,然后排序,在转换成迭代器返回
    partitioned.mapPartitions(it => {
    //将迭代器转换成list,然后排序,在转换成迭代器返回
    it.toList.sortBy(_._2).reverse.take(topN).iterator
    })
  7. 最后action触发计算

代码:

package cn.edu360.day3

import java.net.URL

import org.apache.spark.rdd.RDD
import org.apache.spark.{Partitioner, SparkConf, SparkContext}

import scala.collection.mutable

/**
  * Created by zx on 2017/10/8.
  */
object GroupFavTeacher3 {

  def main(args: Array[String]): Unit = {

    val topN = args(1).toInt

    val conf = new SparkConf().setAppName("GroupFavTeacher2").setMaster("local[4]")
    val sc = new SparkContext(conf)

    //指定以后从哪里读取数据
    val lines: RDD[String] = sc.textFile(args(0))
    //整理数据
    val sbjectTeacherAndOne: RDD[((String, String), Int)] = lines.map(line => {
      val index = line.lastIndexOf("/")
      val teacher = line.substring(index + 1)
      val httpHost = line.substring(0, index)
      val subject = new URL(httpHost).getHost.split("[.]")(0)
      ((subject, teacher), 1)
    })

    //聚合,将学科和老师联合当做key
    val reduced: RDD[((String, String), Int)] = sbjectTeacherAndOne.reduceByKey(_+_)

    //计算有多少学科
    val subjects: Array[String] = reduced.map(_._1._1).distinct().collect()

    //自定义一个分区器,并且按照指定的分区器进行分区
    val sbPatitioner = new SubjectParitioner(subjects);

    //partitionBy按照指定的分区规则进行分区
    //调用partitionBy时RDD的Key是(String, String)
    val partitioned: RDD[((String, String), Int)] = reduced.partitionBy(sbPatitioner)

    //如果一次拿出一个分区(可以操作一个分区中的数据了)
    val sorted: RDD[((String, String), Int)] = partitioned.mapPartitions(it => {
      //将迭代器转换成list,然后排序,在转换成迭代器返回
      it.toList.sortBy(_._2).reverse.take(topN).iterator
    })

    //
    val r: Array[((String, String), Int)] = sorted.collect()

    println(r.toBuffer)


    sc.stop()


  }
}

//自定义分区器
class SubjectParitioner(sbs: Array[String]) extends Partitioner {

  //相当于主构造器(new的时候回执行一次)
  //用于存放规则的一个map
  val rules = new mutable.HashMap[String, Int]()
  var i = 0
  for(sb <- sbs) {
    //rules(sb) = i
    rules.put(sb, i)
    i += 1
  }

  //返回分区的数量(下一个RDD有多少分区)
  override def numPartitions: Int = sbs.length

  //根据传入的key计算分区标号
  //key是一个元组(String, String)
  override def getPartition(key: Any): Int = {
    //获取学科名称
    val subject = key.asInstanceOf[(String, String)]._1
    //根据规则计算分区编号
    rules(subject)
  }
}

分组求TopN案例4

与上个案例相比只有一次shullfe,加快效率

  1. 首先将数据进行切分,将url的形式切分成,将学科、老师数据进行切分,然后进行存储, 构造这样的数据((subject, teacher), 1)
  2. 将(subject, teacher)作为key,对其进行聚合累加,同时调用分区器:reduceByKey(sbPatitioner, +),在此进行了一次shullfe
  3. 然后调用自定义构造器,在构造器中,构造一个hashmap,将subject与数值存入当中,规则是将构造器中的规则重写将输入subject时,输出它的分区编号
  4. 调用partitionBy(sbPatitioner),将构造的分区器传入,则可以重新制定分区规则,然后就会按分区进行分组,一个学科一个分区
  5. 调用mapPartitions方法,对每个分区进行独立的计算,在此进行了一次shullfe
  6. 每个分区中将迭代器转换成list,然后排序,在转换成迭代器返回
    partitioned.mapPartitions(it => {
    //将迭代器转换成list,然后排序,在转换成迭代器返回
    it.toList.sortBy(_._2).reverse.take(topN).iterator
    })
  7. 最后action触发计算
package cn.edu360.day3

import java.net.URL

import org.apache.spark.rdd.RDD
import org.apache.spark.{Partitioner, SparkConf, SparkContext}

import scala.collection.mutable

/**
  * Created by zx on 2017/10/8.
  */
object GroupFavTeacher4 {

  def main(args: Array[String]): Unit = {

    val topN = args(1).toInt

    val conf = new SparkConf().setAppName("GroupFavTeacher2").setMaster("local[4]")
    val sc = new SparkContext(conf)

    //指定以后从哪里读取数据
    val lines: RDD[String] = sc.textFile(args(0))
    //整理数据
    val sbjectTeacherAndOne: RDD[((String, String), Int)] = lines.map(line => {
      val index = line.lastIndexOf("/")
      val teacher = line.substring(index + 1)
      val httpHost = line.substring(0, index)
      val subject = new URL(httpHost).getHost.split("[.]")(0)
      ((subject, teacher), 1)
    })


    //计算有多少学科
    val subjects: Array[String] = sbjectTeacherAndOne.map(_._1._1).distinct().collect()

    //自定义一个分区器,并且按照指定的分区器进行分区
    val sbPatitioner = new SubjectParitioner2(subjects)

    //聚合,聚合是就按照指定的分区器进行分区
    //该RDD一个分区内仅有一个学科的数据
    val reduced: RDD[((String, String), Int)] = sbjectTeacherAndOne.reduceByKey(sbPatitioner, _+_)

    //如果一次拿出一个分区(可以操作一个分区中的数据了)
    val sorted: RDD[((String, String), Int)] = reduced.mapPartitions(it => {
      //将迭代器转换成list,然后排序,在转换成迭代器返回
      it.toList.sortBy(_._2).reverse.take(topN).iterator

      //即排序,有不全部加载到内存

      //长度为5的一个可以排序的集合

    })

    //收集结果
    //val r: Array[((String, String), Int)] = sorted.collect()
    //println(r.toBuffer)

    sorted.saveAsTextFile("/Users/zx/Desktop/out")


    sc.stop()


  }
}

//自定义分区器
class SubjectParitioner2(sbs: Array[String]) extends Partitioner {

  //相当于主构造器(new的时候回执行一次)
  //用于存放规则的一个map
  val rules = new mutable.HashMap[String, Int]()
  var i = 0
  for(sb <- sbs) {
    //rules(sb) = i
    rules.put(sb, i)
    i += 1
  }

  //返回分区的数量(下一个RDD有多少分区)
  override def numPartitions: Int = sbs.length

  //根据传入的key计算分区标号
  //key是一个元组(String, String)
  override def getPartition(key: Any): Int = {
    //获取学科名称
    val subject = key.asInstanceOf[(String, String)]._1
    //根据规则计算分区编号
    rules(subject)
  }
}

发布了44 篇原创文章 · 获赞 0 · 访问量 859

猜你喜欢

转载自blog.csdn.net/heartless_killer/article/details/104571169
今日推荐