SparkCore

  • 一、RDD数据
  • 1.RDD的血缘关系
  • 2.RDD序列化
  • 3.RDD持久化
  • CheckPoin检查点缓存
  • Cache缓存和CheckPoin检查点的区别
  • 二、广播变量
  • 三、累加器
  • 1.系统自带累加器;
  • 2.用户自定义累加器
  • 四、Spark内核调度
  • 1.DAG阶段划分
  • 2.款窄依赖和阶段划分
  • 3.内存迭代计算
  • Spark并行度
  • 4.Spark任务调度
  • 5.Spark概念名词
  • 五、重要理解


一、RDD数据

1.RDD的血缘关系

RDD 只支持粗粒度转换,即在大量记录上执行的单个操作。将创建 RDD 的一系列 Lineage(血统)记录下来,以便恢复丢失的分区。RDD 的 Lineage 会记录 RDD 的元数据信息和转换行为,当该 RDD 的部分分区数据丢失时,它可以根据这些信息来重新运算和恢复丢失的数 据分区。

spark数据仓库实现 spark数据存储在哪_大数据

新的RDD会依赖旧的RDD,RDD记录的是过程数据,只有在使用Action算子时RDD才会通过记录数据真正的执行。

2.RDD序列化

  1. 闭包检查
    从计算的角度, 算子以外的代码都是在 Driver 端执行, 算子里面的代码都是在 Executor端执行。那么在 scala 的函数式编程中,就会导致算子内经常会用到算子外的数据,这样就形成了闭包的效果,如果使用的算子外的数据无法序列化,就意味着无法传值给 Executor端执行,就会发生错误,所以需要在执行任务计算前,检测闭包内的对象是否可以进行序列化,这个操作我们称之为闭包检测。
  2. 序列化方法和属性
    从计算的角度, 算子以外的代码都是在 Driver 端执行, 算子里面的代码都是在 Executor端执行,看如下代码:
def main(args: Array[String]): Unit = {
    //1.创建 SparkConf 并设置 App 名称
    val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")
    //2.创建 SparkContext,该对象是提交 Spark App 的入口
    val sc: SparkContext = new SparkContext(conf)
    //3.创建一个 RDD
    val rdd: RDD[String] = sc.makeRDD(Array("hello world", "hello spark", "hive", "atguigu"))
    //3.1 创建一个 Search 对象
    val search = new Search("hello")
    //3.2 函数传递,打印:ERROR Task not serializable
    search.getMatch1(rdd).collect().foreach(println)
    //3.3 属性传递,打印:ERROR Task not serializable
    search.getMatch2(rdd).collect().foreach(println)
    //如果调用的方法中使用到了没有序列化特质的对象,会报错
    //4.关闭连接
    sc.stop()
  }
  #对象需要序列化后才能进行io传输 Driver->Executor  
  #Scala中的样例类 case class 自动继承了Serializable特质
  #Scala中类的构造参数是类的属性(反编译后才能看到)
  
  class Search(query:String) extends Serializable {
    def isMatch(s: String): Boolean = {
      s.contains(query)
    }

    // 函数序列化案例
    def getMatch1(rdd: RDD[String]): RDD[String] = {
      //rdd.filter(this.isMatch)
      rdd.filter(isMatch)
    }

    def getMatch2(rdd: RDD[String]): RDD[String] = {
      //rdd.filter(x => x.contains(this.query))
      rdd.filter(x => x.contains(query))
      
      //val q = query 将对象的query赋值给q,字符串在io过程中可以序列化
      //rdd.filter(x => x.contains(q))
    }
  }
  1. Kryo 序列化框架
    Java 的序列化能够序列化任何的类。但是比较重(字节多),序列化后,对象的提交也比较大。Spark 出于性能的考虑,Spark2.0 开始支持另外一种 Kryo 序列化机制。Kryo 速度是 Serializable 的 10 倍。当 RDD 在 Shuffle 数据的时候,简单数据类型、数组和字符串类型已经在 Spark 内部使用 Kryo 来序列化。
    注意:即使使用 Kryo 序列化,也要继承 Serializable 接口。

3.RDD持久化

spark数据仓库实现 spark数据存储在哪_hadoop_02

RDD中记录的是过程数据,既RDD的对象能够重复使用,但是数据需要重新计算。
上图中使用了两个collect()算子,实际每一次collect()都需要从rdd1开始进行数据计算。

spark数据仓库实现 spark数据存储在哪_spark_03

cache:将数据临时存储在内存中方便进行数据重用
persist:将数据临时存储在磁盘文件中方便进行数据重用(涉及磁盘IO,性能较低,但是数据安全)
注意:当作业执行完毕,临时保存的数据文件就会丢失

spark数据仓库实现 spark数据存储在哪_spark数据仓库实现_04

缓存有可能丢失,或者存储于内存的数据由于内存不足而被删除,RDD 的缓存容错机制保证了即使缓存丢失也能保证计算的正确执行。通过基于 RDD 的一系列转换,丢失的数据会被重算,由于 RDD 的各个 Partition 是相对独立的,因此只需要计算丢失的部分即可,并不需要重算全部 Partition。Spark 会自动对一些 Shuffle 操作的中间数据做持久化操作(比如:reduceByKey)。这样做的目的是为了当一个节点 Shuffle 失败了避免重新计算整个输入。但是,在实际使用的时候,如果想重用数据,仍然建议调用 persist 或 cache。

RDD 通过 Cache 或者 Persist 方法将前面的计算结果缓存,默认情况下会把数据以缓存在 JVM 的堆内存中。但是并不是这两个方法被调用时立即缓存,而是触发后面的 action 算子时,该 RDD 将会被缓存在计算节点的内存中,并供后面重用。

CheckPoin检查点缓存

CheckPoint:
将数据长久的保存在磁盘文件中
,但是一般情况下,当一个Job被执行完毕后,CheckPoint会再次独立的执行Job,将要保存的阶段数据进行保存。
注意:CheckPoint执行的Job是为了保存数据,通常只会执行到设置检查点的阶段。

检查点其实就是通过将 RDD中间结果写入磁盘由于血缘依赖过长会造成容错成本过高,这样就不如在中间阶段做检查点容错,如果检查点之后有节点出现问题,可以从检查点开始重做血缘,减少了开销。
注意:对 RDD 进行 checkpoint 操作并不会马上被执行,必须执行 Action 操作才能触发。

spark数据仓库实现 spark数据存储在哪_spark_05

Cache缓存和CheckPoin检查点的区别

1)Cache 缓存只是将数据保存起来,不切断血缘依赖。Checkpoint 检查点切断血缘依赖。
2)Cache 缓存的数据通常存储在磁盘、内存等地方,可靠性低。Checkpoint 的数据通常存储在 HDFS 等容错、高可用的文件系统,可靠性高。
3)建议对 checkpoint()的 RDD 使用 Cache 缓存,这样 checkpoint 的 job 只需从 Cache 缓存中读取数据即可,否则需要再从头计算一次 RDD。因此CcheckPoint通常和Cache一起使用。

spark数据仓库实现 spark数据存储在哪_大数据_06

Cache轻量化保存RDD数据,存储在内存或硬盘上,是分散存储,设计上是不安全的(保留血缘关系)
CheckPoin是重量级保存RDD数据,存储在硬盘(HDFS)上,设计上安全(不保留血缘关系)
Cache性能更好,分散存储,由Executor并行执行;CheckPoint比较慢,因为需要由各个Executor使用网络IO存储到HDFS上。

二、广播变量

广播变量用来高效分发较大的对象。向所有工作节点发送一个较大的只读值,以供一个或多个 Spark 操作使用。比如,如果你的应用需要向所有节点发送一个较大的只读查询表,广播变量用起来都很顺手。在多个并行操作中使用同一个变量,但是 Spark 会为每个任务分别发送。

spark数据仓库实现 spark数据存储在哪_spark数据仓库实现_07


spark数据仓库实现 spark数据存储在哪_spark数据仓库实现_08

上述,stu_info_list为Driver上的数据;Driver为资源管理者,Executor为工作进程,其内部可能会含有多个RDD分区;
在上述的map操作上,每个分区都需要申请Driver资源,会导致多次网络IO,然而同一进程内的资源共享,这样的数据申请和访问会操作资源的浪费。

spark数据仓库实现 spark数据存储在哪_数据_09

三、累加器

分布式共享只读变量:

累加器用来把 Executor 端变量信息聚合到 Driver 端。在 Driver 程序中定义的变量,在 Executor 端的每个
Task 都会得到这个变量的一份新的副本,每个 task 更新这些副本的值后, 传回 Driver 端进行 merge。

1.系统自带累加器;

spark数据仓库实现 spark数据存储在哪_数据_10

Spark的分布式导运行导致,map方法在Executor中执行,其中的count能经过序列化传输到Executor上,但是程序的结果在Driver端输出,其没有将count结果收集到Driver中。

def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")
    val sc: SparkContext = new SparkContext(conf)
    val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4))
    //使用Spark自带的累加器
    val sumAcc: LongAccumulator = sc.longAccumulator("sum")
    rdd.foreach{
      num=>{
        sumAcc.add(num)
      }
    )
    println(sumAcc.value) # 10
    sc.stop()
  }

注意(累加器应该放在行动算子中执行):少加:转换算子中调用累加器,如果没有行动算子,那么累加器将不会执行。多加:当调用多个行动算子,累加器会执行多遍。

2.用户自定义累加器

def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")
    val sc: SparkContext = new SparkContext(conf)
    //todo 用户自定义累加器实现wordcount
    val rdd: RDD[String] = sc.makeRDD(List("hello", "spark", "aimyon", "aimer"))

    val wcAcc = new MyAccumulator()

    sc.register(wcAcc,"wordCountAcc")

    rdd.foreach(
      word=>{
        wcAcc.add(word)
      }
    )
    println(wcAcc.value)
    #结果: Map(spark -> 1, aimer -> 1, aimyon -> 1, hello -> 1)
    sc.stop()
  }

  /*
  1.继承 AccumulatorV2并定义泛型
  IN:累加器输入数据类型
  OUT:累加器输出数据类型
   */
  class MyAccumulator extends AccumulatorV2[String,mutable.Map[String,Long]] {
    private var wcMap = mutable.Map[String,Long]()

    //判断是否为初始状态
    override def isZero: Boolean = {
      wcMap.isEmpty
    }

    override def copy(): AccumulatorV2[String, mutable.Map[String, Long]] = {
      new MyAccumulator
    }

    override def reset(): Unit = {
      wcMap.clear()
    }

    //获取累加器的输入
    override def add(word: String): Unit = {
      //获取map中的value,没有则返回0
      val newCnt = wcMap.getOrElse(word,0L) + 1
      //添加到map中
      wcMap.update(word,newCnt)
    }
    //合并累加器
    override def merge(other: AccumulatorV2[String, mutable.Map[String, Long]]): Unit ={
      val map1 = this.wcMap
      val map2 = other.value

      map2.foreach({
        case (word,count)=>{
          val newCnt = map1.getOrElse(word,0L) + count
          map1.update(word,newCnt)
        }
      })

    }
    //返回结果
    override def value: mutable.Map[String, Long] = {
      wcMap
    }
  }

四、Spark内核调度

1.DAG阶段划分

DAG(Directed Acyclic Graph)有向无环图是由点和线组成的拓扑图形,该图形具有方向,不会闭环。例如,DAG 记录了RDD 的转换过程和任务的阶段。

spark数据仓库实现 spark数据存储在哪_数据_11

2.款窄依赖和阶段划分

窄依赖表示每一个父(上游)RDD 的 Partition 最多被子(下游)RDD 的一个 Partition 使用。

宽依赖表示同一个父(上游)RDD 的 Partition 被多个子(下游)RDD 的 Partition 依赖,会引起Shuffle。

spark数据仓库实现 spark数据存储在哪_spark_12

RDD 任务切分中间分为:Application、Job、Stage 和 Task
⚫ Application:初始化一个SparkContext 即生成一个 Application;
⚫ Job:一个 Action 算子就会生成一个 Job;
⚫Stage:Stage 等于宽依赖(ShuffleDependency)的个数加 1;
⚫ Task:一个 Stage 阶段中,最后一个 RDD 的分区个数就是 Task 的个数。
注意:Application->Job->Stage->Task 每一层都是 1 对 n的关系。

3.内存迭代计算

每个Task线程独立完成工作,就是一个内存计算管道PipLine;一个Task只处理一个分区,但能处理多个有血缘关系的RDD的同一分区。

spark数据仓库实现 spark数据存储在哪_hadoop_13

Spark并行度

并行度:设置并行度会改变分区数量,先有的并行度,后有的分区规划;推荐设置为CPU总核心的2-10倍;

spark数据仓库实现 spark数据存储在哪_大数据_14


spark数据仓库实现 spark数据存储在哪_spark_15

4.Spark任务调度

spark数据仓库实现 spark数据存储在哪_spark数据仓库实现_16

组件

作用

DAG调度器

将逻辑的DAG图进行处理,最终得到逻辑上的Task划分(任务划分)

Task调度器

基于DAG Scheduler的产出,规划这些逻辑task应该在哪些物理的executor上运行,以及监控管理它们的运行

5.Spark概念名词

Term

Meaning

Aplictation

帮助用户构建程序到Spark;由一个Driver领导其运行,多个Executor在集群工作组成

Driver Program

管理main()方法的入口,负责构建SparkContext

Cluster manager

一个额外的服务帮助管理集群资源

Deploy mode

部署模式

Worker node

每个node都能够运行程序在集群上

Executor

在Worknode上运行的进程,包含多个Task

Task

工作线程,最小的工作单元

job

归属于Application,一次程序运行的计划

spark数据仓库实现 spark数据存储在哪_spark_17

五、重要理解

spark数据仓库实现 spark数据存储在哪_spark数据仓库实现_18


spark数据仓库实现 spark数据存储在哪_大数据_19