简单的Spark案例——课程学习量统计

2 篇文章 0 订阅

需求:如下图的文件中有很多访问记录,第一列表示访问站点的时间戳,第二列表示访问的站点,中间用制表符分割。这里相当于学习的不同课程,如java,ui,bigdata,android,h5等,其中每门课程又分为子课程,如h5课程分为teacher,course等。现在需要统计每门课程,学习量最高的两门子课程并降序排列。

测试数据下载地址

链接:https://pan.baidu.com/s/1DjeJkeBwfzLUl0iqF5yo8w 密码:hd35 文件为:access.txt

package com.whua

import java.net.URL

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

/**
  * @author: whua 
  * @create: 2018/10/11 18:54
  */
object ProjectCount1 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("ObjectCount1").setMaster("local[*]")
    val sc = new SparkContext(conf)

    //获取数据
    val file: RDD[String] = sc.textFile("/Users/whua/Documents/tmpTest/access.txt")

    //提取url并生成为一个元组
    val urlAndOne: RDD[(String, Int)] = file.map(line => {
      val fields = line.split("\t")
      val url = fields(1)
      (url, 1)
    })

    //把相同的url聚合
    val sumedUrl: RDD[(String, Int)] = urlAndOne.reduceByKey(_ + _)

    //获取学科信息
    val project: RDD[(String, String, Int)] = sumedUrl.map(x => {
      val url = x._1
      val count = x._2
      val project = new URL(url).getHost
      (project, url, count)
    })

    //用学科来分组,聚合后得到结果
    val ans: Array[(String, List[(String, String, Int)])] = project.groupBy(_._1)
      .mapValues(_.toList.sortBy(_._3).reverse.take(2)).collect()
    //   val ans: Array[(String, String, Int)] = project.sortBy(_._3,false).take(3)
    for (item <- ans) {
      println(item)
    }
    sc.stop()
  }
}

上面的代码是比较基础的代码,接下来在此基础上,引入缓存机制分区器,缓存机制主要是将常用的数据缓存起来,再次调用的时候效率较高;而分区器是为了解决数据倾斜的问题,在结果生成的文件中,我么可以看到,有的文件中产生的数据量多,有的文件中产生的数据量少,分区器是解决数据倾斜的方法之一。下面我们将调用Spark自带的哈希分区器,显而易见,是采用哈希的方式来放置数据产生的位置。

package com.whua

import java.net.URL

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

/**
  * @author: whua 
  * @create: 2018/10/11 20:13
  */
object ProjectCount2 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("ObjectCount1").setMaster("local[*]")
    val sc = new SparkContext(conf)

    //获取数据
    val file: RDD[String] = sc.textFile("/Users/whua/Documents/tmpTest/access.txt")

    //提取url并生成为一个元组
    val urlAndOne: RDD[(String, Int)] = file.map(line => {
      val fields = line.split("\t")
      val url = fields(1)
      (url, 1)
    })

    //把相同的url聚合
    val sumedUrl: RDD[(String, Int)] = urlAndOne.reduceByKey(_ + _)

    //获取学科信息并缓存
    val cachedProject: RDD[(String, (String, Int))] = sumedUrl.map(x => {
      val url = x._1
      val count = x._2
      val project = new URL(url).getHost
      (project, (url, count))
    }).cache()

    //调用Spark自带的分区器此时会发生哈希碰撞,需要自定义分区器
    val ans: RDD[(String, (String, Int))] = cachedProject.partitionBy(new HashPartitioner(3))
    ans.saveAsTextFile("/Users/whua/Documents/tmpTest/out")

    sc.stop()
  }
}

但是,有时候在不同的数据,不同的实际问题中,自带定分区器不一定能很好地解决数据倾斜的问题,这时候就需要自定义分区器,自定义的分区器主要是继承Partitioner抽象类,重写两个方法。这里我们根据不同的课程名来进行分区,即相同的课程名的记录写到同一个文件中。

package com.whua

import java.net.URL

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

import scala.collection.mutable

/**
  * @author: whua 
  * @create: 2018/10/11 20:41
  */
object ProjectCount3 {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("ObjectCount1").setMaster("local[*]")
    val sc = new SparkContext(conf)

    //获取数据
    val file: RDD[String] = sc.textFile("/Users/whua/Documents/tmpTest/access.txt")

    //提取url并生成为一个元组
    val urlAndOne: RDD[(String, Int)] = file.map(line => {
      val fields = line.split("\t")
      val url = fields(1)
      (url, 1)
    })

    //把相同的url聚合
    val sumedUrl: RDD[(String, Int)] = urlAndOne.reduceByKey(_ + _)

    //获取学科信息并缓存
    val cachedProject: RDD[(String, (String, Int))] = sumedUrl.map(x => {
      val url = x._1
      val count = x._2
      val project = new URL(url).getHost
      (project, (url, count))
    }).cache()

    //得到所有学科
    val projects: Array[String] = cachedProject.keys.distinct().collect()
    //调用自定义分区器并得到分区号
    val partitioner: ProjectPartitioner = new ProjectPartitioner(projects)
    //分区
    val partitioned: RDD[(String, (String, Int))] = cachedProject.partitionBy(partitioner)
    //每个分区的数据进行排序并取top2
    val ans: RDD[(String, (String, Int))] = partitioned.mapPartitions(it => {
      it.toList.sortBy(_._2._2).reverse.take(2).iterator
    })

    ans.saveAsTextFile("/Users/whua/Documents/tmpTest/out")
    sc.stop()
  }
}

class ProjectPartitioner(projects: Array[String]) extends Partitioner {
  //用来存放学科和分区号
  private val projectsAndPartNum = new mutable.HashMap[String, Int]
  //计数器,用于指定分区号
  var n = 0

  for (pro <- projects) {
    //HashMap插入
    projectsAndPartNum += (pro -> n)
    n += 1
  }

  //得到分区数
  override def numPartitions: Int = projects.length

  //得到分区号
  override def getPartition(key: Any): Int = {
    projectsAndPartNum.getOrElse(key.toString, 0)
  }
}

结果如下:

 

  • 3
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值