Spark

1.Spark任务架构

1.1 架构组件

1.1.1 Driver

是一个 JVM 进程, 负责执行 Spark 任务的 main 方法

执行用户提交的代码,创建 SparkContext 或者 SparkSession

将用户代码转化为 Spark 任务(Jobs)创建血缘(Lineage),逻辑计划(Logical Plan)和物理计划(Physical Plan)

在 Cluster Manager 的辅助下,把 task 任务分发调度出去

跟踪任务的执行情况,收集日志

1.1.2 Spark Context/Session

它是由 Spark driver 创建,每个 Spark 应用对应一个

程序和集群交互的入口

可以连接到 Cluster Manager 

1.1.3 Cluster Manager 

负责部署整个 Spark 集群

包括上面提到的 driver 和 executors

具有以下几种常见的部署模式: Standalone   YARN   Mesos   Kubernetes

1.1.4 Executor

一个创建在 worker 节点的进程

一个 Executor 有多个 slots(线程)

一个 slot 就是一个线程,对应了一个 task

可以并发执行多个 tasks  stage,stage就是可以并行的task

负责执行 Spark 任务,把结果返回给 Driver

可以将数据缓存到 worker 节点的内存 

1.2 任务架构 

Spark_第1张图片

 一个行为算子会触发一个job,一个job包含多个stage,一个stage是多个可以并行执行的task,task的数量跟分区数量有关,最后生成的文件也与task有关

2.RDD

弹性分布式数据集

使用起来类似Scala中的List集合,但是RDD中不保存数据,在大数据环境下,数据量比较大,不适合复制

五大特性

RDD是由一系列分区组成的

Task是作用在每个分区上的,每个分区至少需要一个Task

RDD之间是有一系列依赖关系的,可以按有无Shuflle分为:宽依赖、窄依赖

分区器是作用在KV格式的RDD上的

Spark为每个Task尽可能的提供最佳计算位置 ,移动计算,不移动数据

分区数:可以设置最小分区数,如果生成分区数超过最小分区数,则此时分区数为生成分区数,如果小于,则分区数为最小分区数。生成的分区数的数量与切片数量一致,一个文件至少对应一个分区。本地模式最小分区数为1,集群模式中最小分区数为2。一个分区对应一个任务

WordCount程序处理流程

Spark_第2张图片

3.Spark算子分类

3.1 Transformation 变换/转换算子

该类型还可继续细分

基于Value数据类型的Transformation算子 例如:map、flatMap、filter等

基于Key-Value数据类型的Transfromation算子 例如:groupByKey、reduceByKey、join等

转换算子并不会触发提交作业,需要由Action算子触发执行 —— 懒执行

3.2 Action 行为算子

该类算子会触发 SparkContext 提交 Job 作业 一个Action算子对应一个Job

例如:foreach、count、take、collect、reduce等 

3.3 常用转换算子

3.3.1 map

package com.shujia.core

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

object Demo03Map {
  /**
   * Spark中的基于RDD的方法可以称之为算子
   * 算子可以分为两类:
   * 1、转换算子:RDD与RDD直接的转换
   * 2、行为算子:每一个行为算子就会触发一个Job
   * 转换算子是懒执行的,如果没有行为算子触发,那么转换算子是不会被执行的
   * 如何区分转换算子和行为算子?
   * 观察调用算子之后返回的类型,如果是RDD则该算子是转换算子,如果是其他数据类型则该算子是行为算子
   */
  def main(args: Array[String]): Unit = {
    val list: List[Int] = List(1, 2, 3, 4, 5)
    list.map(i => {
      println("map方法被执行了")
      i
    }).foreach(println)

    val conf: SparkConf = new SparkConf()
    conf.setMaster("local")
    conf.setAppName("Demo03Map")

    val sc: SparkContext = new SparkContext(conf)

    // 通过Scala中的集合构建RDD
    val listRDD: RDD[Int] = sc.parallelize(list)
    listRDD
      /*
       * map 方法需要接收一个函数f
       * 函数f:Int => 自定义类型
       * Int类型同RDD中的每一条数据的类型有关
       * 函数f的返回值类型由自己决定
       * 传入一条数据返回一条数据
       */
      .map(i => {
        println("RDD的map方法被执行了")
        i
      }).foreach(println)


  }

}

3.3.2 flatmap

package com.shujia.core

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

object Demo04FlatMap {
  def main(args: Array[String]): Unit = {
    /**
     * flatMap:转换算子,需要接收一个函数f
     * 返回值类型有特殊要求,必须是数组或者是集合类
     * 会对返回的数组或者是集合进行扁平化处理,即展开
     * 传入一条数据返回多条数据
     */
    val conf: SparkConf = new SparkConf()
    conf.setMaster("local")
    conf.setAppName("Demo04FlatMap")

    val sc: SparkContext = new SparkContext(conf)

    val wordList: List[String] = List[String]("java,java,java", "scala,scala", "python")
    val lineRDD: RDD[String] = sc.parallelize(wordList)
    lineRDD.flatMap(line=>line.split(",")).foreach(println)


  }

}

3.3.3 filter

package com.shujia.core

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

object Demo05Filter {
  def main(args: Array[String]): Unit = {
    /**
     * filter:转换算子,可以实现对RDD的数据进行过滤
     * 需要接受一个函数f,返回值类型必须是布尔类型
     * 如果返回的是true,则保留数据
     * 如果返回的是false,则过滤数据
     */

    // 基于students.txt数据,过滤出理科五班的所有学生
    val conf: SparkConf = new SparkConf()
    conf.setMaster("local")
    conf.setAppName("Demo05Filter")

    val sc: SparkContext = new SparkContext(conf)
    val stuRDD: RDD[String] = sc.textFile("spark/data/stu/students.txt")

    stuRDD.filter(stu => stu.split(",")(4) == "理科五班").foreach(println)

  }

}

 3.3.4 sample

package com.shujia.core

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

object Demo06Sample {
  def main(args: Array[String]): Unit = {
    /**
     * sample:转换算子,用于对数据进行抽样
     * 可以接收三个参数:
     * withReplacement:有无放回
     * fraction:抽样比例,小数,最终返回的数据条数并不固定,但是差别不大,在一个数量级
     * seed:随机数种子,如果该值固定,则每次抽样的结果不变,默认等于随机的一个Long类型值
     */
    val conf: SparkConf = new SparkConf()
    conf.setMaster("local")
    conf.setAppName("Demo06Sample")

    val sc: SparkContext = new SparkContext(conf)
    val stuRDD: RDD[String] = sc.textFile("spark/data/stu/students.txt")

    // 对学生数据进行抽样,无放回,抽样10条数据
    stuRDD.sample(withReplacement = false, fraction = 0.01, seed = 1).foreach(println)
  }

}

3.3.5Union

package com.shujia.core

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

object Demo07Union {
  def main(args: Array[String]): Unit = {
    /**
     * union:转换算子,类似SQL中的union all
     * 可以将两个同类型的RDD进行合并
     * Spark中的union操作并不会对数据进行去重
     * 如果需要去重可以使用distinct算子
     *
     * distinct:转换算子,可以对RDD的数据进行去重
     */

    val conf: SparkConf = new SparkConf()
    conf.setMaster("local")
    conf.setAppName("Demo07Union")

    val sc: SparkContext = new SparkContext(conf)
    val stuRDD: RDD[String] = sc.textFile("spark/data/stu/students.txt")


    val stuRDD01: RDD[String] = stuRDD.sample(withReplacement = false, fraction = 0.01, seed = 1)
    println(stuRDD01.getNumPartitions)
    val stuRDD02: RDD[String] = stuRDD.sample(withReplacement = false, fraction = 0.01, seed = 1)
    println(stuRDD02.getNumPartitions)

    // union之后得到的RDD分区数等于两个RDD分区数之和
    val unionRDD: RDD[String] = stuRDD01.union(stuRDD02)
    println(unionRDD.getNumPartitions)

    unionRDD.foreach(println)

    val distinctUnionRDD: RDD[String] = unionRDD.distinct()
    distinctUnionRDD.foreach(println)

  }

}

3.3.6 cartesian 笛卡尔积

package com.shujia.core

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

object Demo08Cartesian {
  def main(args: Array[String]): Unit = {
    /**
     * cartesian:转换算子
     * 可以对两份数据做笛卡尔积
     */

    val conf: SparkConf = new SparkConf()
    conf.setMaster("local")
    conf.setAppName("Demo08Cartesian")

    val sc: SparkContext = new SparkContext(conf)

    val intList: List[Int] = List[Int](1, 2, 3, 4, 5)
    val strList: List[String] = List[String]("a","b","c")

    val intRDD: RDD[Int] = sc.parallelize(intList)
    val strRDD: RDD[String] = sc.parallelize(strList)

    val cartesianRDD: RDD[(Int, String)] = intRDD.cartesian(strRDD)
    cartesianRDD.foreach(println)

  }

}

3.3.7 groupby

package com.shujia.core

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

object Demo09GroupBy {
  def main(args: Array[String]): Unit = {
    /**
     * groupBy:转换算子
     * 需要通过函数指定一个字段进行分组
     */
    val conf: SparkConf = new SparkConf()
    conf.setMaster("local")
    conf.setAppName("Demo09GroupBy")

    val sc: SparkContext = new SparkContext(conf)
    val stuRDD: RDD[Stu] = sc
      .textFile("spark/data/stu/students.txt")
      .map(line => {
        val splits: Array[String] = line.split(",")
        Stu(splits(0), splits(1), splits(2).toInt, splits(3), splits(4))
      })

    // 按照班级分组
    val stuGrpRDD: RDD[(String, Iterable[Stu])] = stuRDD.groupBy(_.clazz)
    stuGrpRDD.foreach(println)

    // 统计班级人数
    stuGrpRDD
      .map(kv => s"${kv._1},${kv._2.size}")
      .foreach(println)

  }
  case class Stu(id: String, name: String, age: Int, gender: String, clazz: String)

}

3.3.8 groupbykey

package com.shujia.core

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

object Demo10GroupByKey {
  def main(args: Array[String]): Unit = {
    /**
     * groupByKey:转换算子
     * 首先只有KV格式的RDD才能调用groupByKey算子
     * 可以将KV格式的RDD按照Key进行分组
     */
    val conf: SparkConf = new SparkConf()
    conf.setMaster("local")
    conf.setAppName("Demo10GroupByKey")

    val sc: SparkContext = new SparkContext(conf)
    val stuRDD: RDD[Stu] = sc
      .textFile("spark/data/stu/students.txt")
      .map(line => {
        val splits: Array[String] = line.split(",")
        Stu(splits(0), splits(1), splits(2).toInt, splits(3), splits(4))
      })

    // 将数据变成KV格式
    // 按照班级分组
    val stuKVRDD: RDD[(String, Stu)] = stuRDD.map(stu => (stu.clazz, stu))
    val stuGrpRDD: RDD[(String, Iterable[Stu])] = stuKVRDD.groupByKey()
    stuGrpRDD.foreach(println)

    // 统计班级人数
    stuGrpRDD
      .map(kv => s"${kv._1},${kv._2.size}")
      .foreach(println)

  }

  case class Stu(id: String, name: String, age: Int, gender: String, clazz: String)

}

3.3.9 reducebykey

package com.shujia.core

import com.shujia.core.Demo09GroupBy.Stu
import com.shujia.core.Demo10GroupByKey.Stu
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.rdd.RDD

object Demo11ReduceByKey {
  def main(args: Array[String]): Unit = {
    /**
     * reduceByKey:转换算子 需要接收一个聚合函数
     * 首先只有KV格式的RDD才能调用reduceByKey算子
     * 可以将KV格式的RDD按照Key进行分组并且同时进行聚合操作
     * 聚合操作需要通过函数进行传入
     * 同时也会将聚合函数用于预聚合
     */

    val conf: SparkConf = new SparkConf()
    conf.setMaster("local")
    conf.setAppName("Demo11ReduceByKey")

    val sc: SparkContext = new SparkContext(conf)

    // 按照班级分组并且统计班级人数
    val stuKVRDD: RDD[(String, Int)] = sc
      .textFile("spark/data/stu/students.txt")
      // 将数据变成KV格式,以班级作为Key,1作为Value
      .map(line => {
        val splits: Array[String] = line.split(",")
        (splits(4), 1)
      })

    stuKVRDD
      .groupByKey()
      .map(kv => {
        val clazz: String = kv._1
        //        val cnt: Int = kv._2.size
        val cnt: Int = kv._2.sum
        s"$clazz,$cnt"
      }).foreach(println)

    // 使用reduceByKey进行简化
    stuKVRDD
      // reduceByKey无法直接完成avg聚合操作
      // reduceByKey 可以实现预聚合操作
      //      .reduceByKey((i1: Int, i2: Int) => i1 + i2)
      .reduceByKey(_ + _) // 基于匿名函数的省略规则进行简化
      .map(kv => s"${kv._1},${kv._2}")
      .foreach(println)

    // 使用aggregateByKey统计班级的平均年龄
    //    stuKVRDD.aggregateByKey()



  }

}

3.3.10 sortby

package com.shujia.core

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

object Demo12SortBy {
  def main(args: Array[String]): Unit = {
    /**
     * sortBy:转换算子,可以对RDD的数据进行排序
     */
    val conf: SparkConf = new SparkConf()
    conf.setMaster("local")
    conf.setAppName("Demo12SortBy")

    val sc: SparkContext = new SparkContext(conf)
    val stuRDD: RDD[Stu] = sc
      .textFile("spark/data/stu/students.txt")
      .map(line => {
        val splits: Array[String] = line.split(",")
        Stu(splits(0), splits(1), splits(2).toInt, splits(3), splits(4))
      })

    // 按照班级降序排列
    stuRDD.sortBy(_.clazz, ascending = false).foreach(println)

    // 按照班级降序排列 再按照年龄升序排列
    stuRDD.sortBy(stu=>stu.clazz + (1000-stu.age), ascending = false).foreach(println)



  }

  case class Stu(id: String, name: String, age: Int, gender: String, clazz: String)

}

3.3.11 aggregateByKey

package com.bigdata.core

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

object DemoAggregateByKey {
  def main(args: Array[String]): Unit = {
    //转换算子:按照指定的计算逻辑,对key分组后进行预聚合以及聚合

    val conf: SparkConf = new SparkConf()
    conf.setMaster("local")
    conf.setAppName("DemoMap")

    val sc: SparkContext = new SparkContext(conf)

    val stuRDD: RDD[String] = sc.textFile("spark/data/stu/students.txt")

    //取出每行
    val stuInfoRDD: RDD[(String, Int)] = stuRDD.map(line => {
      val splits: Array[String] = line.split(",")
      (splits(4), 1)
    })

    //取出年龄班级
    val stuAgeRDD: RDD[(String, Int)] = stuRDD.map(line => {
      val splits: Array[String] = line.split(",")
      (splits(4), splits(2).toInt)
    })
    /*
    aggregateByKey接收俩组参数
    第一组参数zeroValue:初始化的值,类型基于值推断,可以指定任意的值
    第二组参数需要接受俩个函数:seqOp,combOp
    seqOp:预聚合函数,作用在MapTask内部
    combOp:聚合函数,作用在ReduceTask内部
     */
//    stuInfoRDD.aggregateByKey( 0)((i1,i2)=>i1+i2,(u1,u2)=>u1+u2).foreach(println)

    //通过reduce求平均年龄
    // 先统计班级人数
    val clazzCntRDD: RDD[(String, Int)] = stuInfoRDD.reduceByKey(_ + _)
    // 统计班级总年龄
    val clazzAgeCnt: RDD[(String, Int)] = stuAgeRDD.reduceByKey(_ + _)

    val clazzAgeNum: RDD[(String, (Int, Int))] = clazzCntRDD.join(clazzAgeCnt)

    clazzAgeNum.map(kv=>{
      (kv._1,kv._2._2/kv._2._1.toDouble)
    }).foreach(println)

    println("*"*100)


    /*通过aggregateByKey实现*/
    stuAgeRDD.aggregateByKey((0,0))((t2,age)=>{
      val sumAge: Int = t2._1+age
      val clazzCnt: Int = t2._2 + 1
      (sumAge,clazzCnt)
    },(sumAgeAndClazzCnt01,sumAgeAndClazzCnt02)=>{
      val totalSumAge: Int = sumAgeAndClazzCnt01._1+sumAgeAndClazzCnt02._1
      val totalCnt: Int = sumAgeAndClazzCnt01._2+sumAgeAndClazzCnt02._2
      (totalSumAge,totalCnt)
    }).map(kv => {
      (kv._1, kv._2._1 / kv._2._2.toDouble)
    }).foreach(println)


    //使用reduceByKey优化aggregateByKey
    val totalRDD: RDD[(String, (Int, Int))] = stuRDD.map(line => {
      val splits: Array[String] = line.split(",")
      (splits(4), (splits(2).toInt, 1))
    }).reduceByKey((t1, t2) => {
      val reduceAge: Int = t1._1 + t2._1
      val reduceCnt: Int = t1._2 + t2._2
      (reduceAge, reduceCnt)
    })
    totalRDD.map(kv => {
      (kv._1, kv._2._1 / kv._2._2.toDouble)
    }).foreach(println)
  }

}

3.3.12 join

package com.bigdata.core

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

object DemoJoin {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf()
    conf.setMaster("local")
    conf.setAppName("DemoJoin")

    val sc: SparkContext = new SparkContext(conf)

    val stuRDD: RDD[String] = sc.textFile("spark/data/stu/students.txt")
    val scoRDD: RDD[String] = sc.textFile("spark/data/stu/score.txt")

    val stuNameAge: RDD[(String, (String, Int))] = stuRDD.map(line => {
      val splits: Array[String] = line.split(",")
      (splits(0), (splits(1), splits(2).toInt))
    })

    val sumScoreRDD: RDD[(String, Int)] = scoRDD.map(line => {
      val splits: Array[String] = line.split(",")
      (splits(0), splits(2).toInt)
    }).reduceByKey(_ + _)

    sumScoreRDD.join(stuNameAge).map{
      case (id: String, (sumScore: Int,(name: String, age: Int))) =>
      s"$id,$name,$age,$sumScore"
    }.foreach(println)



    //左连接
    stuRDD.map(line => {
      val splits: Array[String] = line.split(",")
      (splits(0), (splits(1), splits(2).toInt))
    }).leftOuterJoin(sumScoreRDD).map {
      case (id: String, ((name: String, age: Int), mayBeSumScore: Option[Int])) =>
        mayBeSumScore match {
          case Some(sumScore) =>
            s"$id,$name,$age,$sumScore"
          case None =>
            s"$id,$name,$age,0"
        }
    }.foreach(println)


  }

}

3.3.13 mapPartitions

package com.bigdata.core

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

import java.sql.{Connection, DriverManager, PreparedStatement, ResultSet}

object DemoMapPartitions {
  def main(args: Array[String]): Unit = {
    /**
     * Spark的代码是需要以分布式的环境进行运行的
     * 虽然代码都好像是写在main方法当中,但实际上应该可以分为两个部分:算子内部、算子外部
     * 算子内部是以Task的形式发送到Executor中去执行的
     * 算子外部是在Driver端执行的
     * 所以在算子内部如果使用了算子外部定义的变量,则该变量必须实现序列化
     * 而MySQL连接是不能被序列化的,所以就会引发问题 Task not serializable
     * Caused by: java.io.NotSerializableException: com.mysql.jdbc.JDBC42PreparedStatement
     *
     */

    val blackList: List[String] = List[String]("1500100777", "1500100888", "1500100999")

    val conf: SparkConf = new SparkConf()
    conf.setMaster("local")
    conf.setAppName("Demo15MapPartitions")

    val sc: SparkContext = new SparkContext(conf)

    val blackRDD: RDD[String] = sc.parallelize(blackList)

    /** *
     * 如果把MySQL相关的操作都放入Task,虽然可以解决Task没有被序列化的问题,但是又会导致性能问题
     * 因为来一条数据,就需要建立一次连接,频繁地创建销毁连接代价太大了
     * 就需要借助mapPartitions来优化性能
     */

    blackRDD.mapPartitions(iter=> {
      //与数据库建立连接
      val conn: Connection = DriverManager.getConnection("jdbc:mysql://rm-bp1h7v927zia3t8iwho.mysql.rds.aliyuncs.com:3307/stu023", "shujia23", "123456")


      val pst: PreparedStatement = conn.prepareStatement("select id,name,age,gender,clazz from student where id = ?")

      iter.map(id=>{

        var returnStr = ""

        pst.setString(1, id)

        val rs: ResultSet = pst.executeQuery()

        while (rs.next()){
          val id: String = rs.getString("id")
          val name: String = rs.getString("name")
          val age: String = rs.getString("age")
          val gender: String = rs.getString("gender")
          val clazz: String = rs.getString("clazz")
          returnStr = s"$id,$name,$age,$gender,$clazz"
        }
        returnStr
      })
    }).foreach(println)

    /**
     * 对每个RDD的分区进行处理
     * 一个分区一般对应一个切片,一个切片一般会有多条数据
     * 多条数据在Spark中一般会通过Iterator类型进行接收
     *
     * 由于Task是作用在每一个分区上的,mapPartitions算子内部的代码会被执行的次数,由分区数决定
     * 大大减少连接建立的次数,提高性能
     *
     * 适用于需要跟外部数据源建立连接,并且从外部数据源获取数据,然后有后续处理时就可以使用mapPartitions,减少连接建立的次数
     *
     * 类似的:foreachPartition
     * 适用于需要跟外部数据源建立连接,将数据写入外部数据源或者是并且从外部数据源获取数据,但没有后续处理时就可以使用foreachPartition,减少连接建立的次数
     */

  }
}

3.3.14 mapvalues

package com.shujia.core

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

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

    /**
     * mapValues:转换算子,需要作用在KV格式的RDD上,可以对Value进行处理
     */
    val conf: SparkConf = new SparkConf()
    conf.setMaster("local")
    conf.setAppName("Demo16MapValue")

    val sc: SparkContext = new SparkContext(conf)

    val kvRDD: RDD[(String, Int)] = sc.parallelize(List(("k1", 1), ("k2", 2), ("k3", 3)))
    // 将value做次方返回新的value
    kvRDD.mapValues(v => v * v * v).foreach(println)
  }

}

3.3.15 CoGroup

package com.shujia.core

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

object Demo17CoGroup {
  def main(args: Array[String]): Unit = {
    /**
     * cogroup:转换算子,需要作用在KV格式的RDD上
     * 会按照相同的Key对两个RDD进行关联操作
     * 如果未关联上,则置为空,所有的数据都会保留
     * 实际上相当于full join操作
     */
    val conf: SparkConf = new SparkConf()
    conf.setMaster("local")
    conf.setAppName("Demo17CoGroup")

    val sc: SparkContext = new SparkContext(conf)

    val kvRDD01: RDD[(String, Int)] = sc.parallelize(List(("k1", 1), ("k2", 2), ("k3", 3)))
    val kvRDD02: RDD[(String, Int)] = sc.parallelize(List(("k2", 22), ("k3", 33), ("k4", 44)))

    kvRDD01.cogroup(kvRDD02).foreach(println)


  }

}

3.3.16 Action

package com.shujia.core

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

object Demo18Action {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf()
    conf.setMaster("local")
    conf.setAppName("Demo18Action")

    val sc: SparkContext = new SparkContext(conf)

    val stuRDD: RDD[String] = sc.textFile("spark/data/stu/students.txt")

    // foreach:类似map操作,但没有返回值
    stuRDD.foreach(println)

    // take:取n条数据 转换成Scala的数据Array
    // 这里调用的foreach操作实际上是由Array数组提供的
    stuRDD.take(10).foreach(println)

    // collect:将RDD所有的数据转换成Scala的Array
    stuRDD.collect().foreach(println)
    stuRDD.map(line => (line.split(",")(4), 1)).collect().toMap.foreach(println)

    // reduce:把数据整体看成一组,进行聚合操作
    val sumAge: Int = stuRDD.map(line => line.split(",")(2).toInt).reduce(_ + _)
    val cnt: Int = stuRDD.map(line => 1).reduce(_ + _)
    println(sumAge / cnt.toDouble)

    // count:统计RDD数据量
    println(stuRDD.count())

    // reduceByKeyLocally:相当于reduceByKey+collect+toMap
    val clazzCntMap: collection.Map[String, Int] = stuRDD.map(line => (line.split(",")(4), 1)).reduceByKeyLocally(_ + _)
    clazzCntMap.foreach(println)
    val clazzCntMap02: Map[String, Int] = stuRDD.map(line => (line.split(",")(4), 1)).reduceByKey(_ + _).collect().toMap
    clazzCntMap02.foreach(println)

    // lookup:作用在KV格式的RDD上,需要指定一个Key,可以将Key对应的所有的Value取出来,并且构建成Scala中的本地集合
    println(stuRDD.map(line=>(line.split(",")(4), 1)).lookup("文科一班"))

    // 保存文件相关的:saveAsTextFile、saveAsObjectFile


  }

}

3.3.17 mapjoin

package com.bigdata.core

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

object DemoMapJoin {
  /**
   * 通过mapjoin完成map任务实现join操作
   * 适用于大表关联小表
   * 原理:将小表进行广播,广播给每一个map任务
   * @param args
   */
  def main(args: Array[String]): Unit = {

    //需求:统计学生总分 输出为id,name,clazz,sumScore
    val conf: SparkConf = new SparkConf()
    conf.setMaster("local")
    conf.setAppName("Demo19MapJoin")

    val sc: SparkContext = new SparkContext(conf)

    val stuRDD: RDD[String] = sc
      .textFile("spark/data/stu/students.txt")

    val scoreRDD: RDD[String] = sc
      .textFile("spark/data/stu/score.txt")

    //求出每个学生的总分
    val sumScoreRDD: RDD[(String, Int)] = scoreRDD.map(line => {
      val splits: Array[String] = line.split(",")
      (splits(0), splits(2).toInt)
    }).reduceByKey(_ + _)

    //不可以在RDD中嵌套RDD,故需要先将RDD转换成map
    val sumScoreMap: Map[String, Int] = sumScoreRDD.collect().toMap

    //在此处通过map的getOrElse来获取上部中被转化的map中的学生总分
    stuRDD.map(line=>{
      val splits: Array[String] = line.split(",")
      val id: String = splits(0)
      val sumScore: Int = sumScoreMap.getOrElse(id, 0)
      s"$id,${splits(1)},${splits(4)},$sumScore"
    }).foreach(println)
  }
}

3.3.18 foreachPartition

package com.shujia.core

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

import java.sql.{Connection, DriverManager, PreparedStatement}

object Demo20ForeachPartition {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf()
    conf.setMaster("local")
    conf.setAppName("Demo20ForeachPartition")

    val sc: SparkContext = new SparkContext(conf)

    // 统计班级人数,将最后的结果保存到MySQL中
    val clazzCntRDD: RDD[(String, Int)] = sc.textFile("spark/data/stu/students.txt")
      .map(line => (line.split(",")(4), 1))
      .reduceByKey(_ + _)

    clazzCntRDD
      // 使用foreach操作同样会出现 连接建立过多的问题 性能比较低
      .foreach(t2 => {
        val clazz: String = t2._1
        val cnt: Int = t2._2

        // 建立连接
        val conn: Connection = DriverManager.getConnection("jdbc:mysql://rm-bp1h7v927zia3t8iwho.mysql.rds.aliyuncs.com:3307/stu023", "shujia23", "123456")

        // 创建Statement
        val pSt: PreparedStatement = conn.prepareStatement("insert into clazz_cnt(clazz,cnt) values (?,?)")

        // 设置参数
        pSt.setString(1, clazz)
        pSt.setInt(2, cnt)

        // 如果有大量数据插入时,可以使用批量插入
        pSt.addBatch() // 将每一次插入放入Batch中

        pSt.executeBatch() // 执行批量插入

      })

    // 使用foreachPartition大大减少连接建立的次数
    clazzCntRDD
      .foreachPartition(iter => {
        // 建立连接
        val conn: Connection = DriverManager.getConnection("jdbc:mysql://rm-bp1h7v927zia3t8iwho.mysql.rds.aliyuncs.com:3307/stu023", "shujia23", "123456")
        // 创建Statement
        val pSt: PreparedStatement = conn.prepareStatement("insert into clazz_cnt(clazz,cnt) values (?,?)")
        iter.foreach(t2 => {
          val clazz: String = t2._1
          val cnt: Int = t2._2
          // 设置参数
          pSt.setString(1, clazz)
          pSt.setInt(2, cnt)

          // 如果有大量数据插入时,可以使用批量插入
          pSt.addBatch() // 将每一次插入放入Batch中
        })
        pSt.executeBatch() // 执行批量插入
      })
  }

}

你可能感兴趣的:(Spark,spark,大数据,分布式)