Spark小笔记-RDD一些重要的事

本文概述了RDD的核心属性,包括分区列表、分区计算函数、依赖关系(窄依赖与宽依赖)、阶段划分和持久化策略(Cache与Checkpoint)。讲解了序列化机制、血缘关系在容错中的作用,以及如何通过自定义分区器和首选位置优化数据分布。
摘要由CSDN通过智能技术生成

1、RDD的核心属性

  • 分区列表

RDD数据结构中存在分区列表,用于执行任务时并行计算,是实现分布式计算的重要属性。

  • 分区计算函数

 Spark在计算时,是使用分区函数对每一个分区进行计算

  • RDD之间的依赖关系

RDD是计算模型的封装,当需求中需要将多个计算模型进行组合时,就需要将多个RDD建立依赖关系

  • 分区器(可选)

当数据为KV类型数据时,可以通过设定分区器自定义数据的分区 

  • 首选位置(可选)

计算数据时,可以根据计算节点的状态选择不同的节点位置进行计算

2、RDD序列化

2.1闭包检查

        一个spark任务,算子以外的代码都是在Driver端执行,算子里面的代码都是在Executor端执行,而在编写的spark代码中,算子中会经常用到算子外的数据,这样就会形成闭包的效果,如果该数据无法序列化,就无法传值给Executor端执行,导致发生错误,所以需要在执行任务计算前,检测闭包内的对象是否可以进行序列化,这个操作就是闭包检测

2.2 检测源码

val conf = new SparkConf().setMaster("local[*]").setAppName("RDD")
val sc = new SparkContext(conf)
val rdd = sc.makeRDD(List(1,2,3,4))
rdd.collect() // new job
sc.stop()

 

 

 

 

 

 

 

 3、RDD的依赖关系

3.1 血缘关系

 相邻的两个RDD之间的关系,称之为依赖关系,多个连续的依赖关系称之为血缘关系


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

val lines = sc.textFile("data/word.txt")
println(lines.toDebugString)
println("*******************************")

val words = lines.flatMap(_.split(" "))
println(words.toDebugString)
println("*******************************")

val wordToOne = words.map((_,1))
println(wordToOne.toDebugString)
println("*******************************")

val wordToCount = wordToOne.reduceByKey(_+_)
println(wordToCount.toDebugString)
println("*******************************")

wordToCount.collect().foreach(println)

sc.stop()
(3) data/word.txt MapPartitionsRDD[1] at textFile at Spark01_WordCount_Dep.scala:12 []
 |  data/word.txt HadoopRDD[0] at textFile at Spark01_WordCount_Dep.scala:12 []
*******************************
(3) MapPartitionsRDD[2] at flatMap at Spark01_WordCount_Dep.scala:16 []
 |  data/word.txt MapPartitionsRDD[1] at textFile at Spark01_WordCount_Dep.scala:12 []
 |  data/word.txt HadoopRDD[0] at textFile at Spark01_WordCount_Dep.scala:12 []
*******************************
(3) MapPartitionsRDD[3] at map at Spark01_WordCount_Dep.scala:20 []
 |  MapPartitionsRDD[2] at flatMap at Spark01_WordCount_Dep.scala:16 []
 |  data/word.txt MapPartitionsRDD[1] at textFile at Spark01_WordCount_Dep.scala:12 []
 |  data/word.txt HadoopRDD[0] at textFile at Spark01_WordCount_Dep.scala:12 []
*******************************
(3) ShuffledRDD[4] at reduceByKey at Spark01_WordCount_Dep.scala:24 []
 +-(3) MapPartitionsRDD[3] at map at Spark01_WordCount_Dep.scala:20 []
    |  MapPartitionsRDD[2] at flatMap at Spark01_WordCount_Dep.scala:16 []
    |  data/word.txt MapPartitionsRDD[1] at textFile at Spark01_WordCount_Dep.scala:12 []
    |  data/word.txt HadoopRDD[0] at textFile at Spark01_WordCount_Dep.scala:12 []
*******************************



说明:
每个RDD都保存了该RDD完整的血缘信息
(3) 表示 3 个分区
+-(3) 表示遇到shuffle了,形成新的阶段,新的阶段 分区也是 3

血缘关系有助于任务遇到错误时,重新回滚任务,重新执行,提高整个程序的容错性

 3.2 依赖关系 

窄依赖:表示每一个父(上游)RDD的Partition最多被子(下游)RDD的一个Partition使用,窄依赖我们形象的比喻为独生子女

class OneToOneDependency[T](rdd: RDD[T]) extends NarrowDependency[T](rdd) 

宽依赖:宽表示同一个父(上游)RDD的Partition被多个子(下游)RDD的Partition依赖,会引起Shuffle,总结:宽依赖我们形象的比喻为多生

class ShuffleDependency[K: ClassTag, V: ClassTag, C: ClassTag](
    @transient private val _rdd: RDD[_ <: Product2[K, V]],
    val partitioner: Partitioner,
    val serializer: Serializer = SparkEnv.get.serializer,
    val keyOrdering: Option[Ordering[K]] = None,
    val aggregator: Option[Aggregator[K, V, C]] = None,
    val mapSideCombine: Boolean = false)
  extends Dependency[Product2[K, V]] 

代码 

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

        val lines = sc.textFile("data/word.txt")
        println(lines.dependencies) // OneToOneDependency
        println("*******************************")

        val words = lines.flatMap(_.split(" "))
        println(words.dependencies) // OneToOneDependency
        println("*******************************")

        val wordToOne = words.map((_,1)) // OneToOneDependency
        println(wordToOne.dependencies)
        println("*******************************")

        val wordToCount = wordToOne.reduceByKey(_+_) //
        println(wordToCount.dependencies)
        println("*******************************")

        wordToCount.collect().foreach(println)
        wordToCount.foreach(println)

        // 依赖的关系主要分为两大类:窄依赖(OneToOneDependency) & 宽依赖(ShuffleDependency)

        sc.stop()
List(org.apache.spark.OneToOneDependency@5e519ad3)
*******************************
List(org.apache.spark.OneToOneDependency@62dbe64e)
*******************************
List(org.apache.spark.OneToOneDependency@50850539)
*******************************
List(org.apache.spark.ShuffleDependency@6801b414)
*******************************

4、阶段划分

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

 4.1 源码

 

 

 

5、任务划分

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的关系。

5.1 源码

 

 

 

 

 ​​​​​​

  

 

 

 

小结:

 阶段的数量等于shuffle操作的个数+ 1

任务的数量应该是所有的阶段的最后一个RDD的分区数之和

 

 6、持久化

6.1 RDD Cache缓存

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

// cache操作会增加血缘关系,不改变原有的血缘关系
println(wordToOneRdd.toDebugString)
// 数据缓存。
wordToOneRdd.cache()
// 可以更改存储级别
//mapRdd.persist(StorageLevel.MEMORY_AND_DISK_2)

存储级别

object StorageLevel {
  val NONE = new StorageLevel(false, false, false, false)
  val DISK_ONLY = new StorageLevel(true, false, false, false)
  val DISK_ONLY_2 = new StorageLevel(true, false, false, false, 2)
  val MEMORY_ONLY = new StorageLevel(false, true, false, true)
  val MEMORY_ONLY_2 = new StorageLevel(false, true, false, true, 2)
  val MEMORY_ONLY_SER = new StorageLevel(false, true, false, false)
  val MEMORY_ONLY_SER_2 = new StorageLevel(false, true, false, false, 2)
  val MEMORY_AND_DISK = new StorageLevel(true, true, false, true)
  val MEMORY_AND_DISK_2 = new StorageLevel(true, true, false, true, 2)
  val MEMORY_AND_DISK_SER = new StorageLevel(true, true, false, false)
  val MEMORY_AND_DISK_SER_2 = new StorageLevel(true, true, false, false, 2)
  val OFF_HEAP = new StorageLevel(true, true, true, false, 1)

         缓存有可能丢失,或者存储于内存的数据由于内存不足而被删除,RDD的缓存容错机制保证了即使缓存丢失也能保证计算的正确执行。通过基于RDD的一系列转换,丢失的数据会被重算,由于RDD的各个Partition是相对独立的,因此只需要计算丢失的部分即可,并不需要重算全部Partition。

        Spark会自动对一些Shuffle操作的中间数据做持久化操作(比如:reduceByKey)。

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

val lines = sc.makeRDD(
            List("Hadoop Hive Hbase", "Spark scala Java")
)
val words = lines.flatMap(_.split(" "))
val wordToOne = words.map(
            t => {
                println("*************************")
                (t, 1)
            }
)
// 设定数据持久化
// cache方法可以将血缘关系进行修改,添加一个和缓存相关的依赖关系
// cache操作不安全。
wordToOne.cache()

val wordToCount = wordToOne.reduceByKey(_+_)
println(wordToCount.toDebugString)
wordToCount.collect()//.foreach(println)
println("--------------------------------------------")
println(wordToCount.toDebugString)

sc.stop()
(12) ShuffledRDD[3] at reduceByKey at Spark01_RDD_Persist.scala:32 []
 +-(12) MapPartitionsRDD[2] at map at Spark01_RDD_Persist.scala:19 []
    |   MapPartitionsRDD[1] at flatMap at Spark01_RDD_Persist.scala:18 []
    |   ParallelCollectionRDD[0] at makeRDD at Spark01_RDD_Persist.scala:15 []
*************************
*************************
*************************
*************************
*************************
*************************
--------------------------------------------
(12) ShuffledRDD[3] at reduceByKey at Spark01_RDD_Persist.scala:32 []
 +-(12) MapPartitionsRDD[2] at map at Spark01_RDD_Persist.scala:19 []
    |       CachedPartitions: 12; MemorySize: 720.0 B; ExternalBlockStoreSize: 0.0 B; DiskSize: 0.0 B
    |   MapPartitionsRDD[1] at flatMap at Spark01_RDD_Persist.scala:18 []
    |   ParallelCollectionRDD[0] at makeRDD at Spark01_RDD_Persist.scala:15 []

cache方法可以将血缘关系进行修改,添加一个和缓存相关的依赖关系

6.2 RDD CheckPoint检查点

所谓的检查点其实就是通过将RDD中间结果写入磁盘

由于血缘依赖过长会造成容错成本过高,这样就不如在中间阶段做检查点容错,如果检查点之后有节点出现问题,可以从检查点开始重做血缘,减少了开销。

  val conf = new SparkConf().setMaster("local[*]").setAppName("WordCount")
        val sc = new SparkContext(conf)
        sc.setCheckpointDir("cp")

        val lines = sc.makeRDD(
            List("Hadoop Hive Hbase", "Spark scala Java")
        )
        val words = lines.flatMap(_.split(" "))
        val wordToOne = words.map(
            t => {
                println("*************************")
                (t, 1)
            }
        )

        // Spark可以将中间的计算结果保存到检查点中,让其他的应用使用数据
        // Checkpoint directory has not been set in the SparkContext
        // 检查点可以切断血缘关系。
        // 检查点为了数据的安全,会重新再执行一遍作业,所以会执行2次
        // 为了解决这个问题,可以将检查点和缓存联合使用
        wordToOne.cache()
        wordToOne.checkpoint()

        val wordToCount = wordToOne.reduceByKey(_+_)
        println(wordToCount.toDebugString)
        wordToCount.collect()//.foreach(println)
        println("--------------------------------------------")
       val rdd2: RDD[(Int, Iterable[(String, Int)])] = wordToOne.groupBy(_._2)
       rdd2.collect()
        println(wordToCount.toDebugString)

        sc.stop()
(12) ShuffledRDD[3] at reduceByKey at Spark03_RDD_Persist.scala:34 []
 +-(12) MapPartitionsRDD[2] at map at Spark03_RDD_Persist.scala:19 []
    |   MapPartitionsRDD[1] at flatMap at Spark03_RDD_Persist.scala:18 []
    |   ParallelCollectionRDD[0] at makeRDD at Spark03_RDD_Persist.scala:15 []
*************************
*************************
*************************
*************************
*************************
*************************
--------------------------------------------
(12) ShuffledRDD[3] at reduceByKey at Spark03_RDD_Persist.scala:34 []
 +-(12) MapPartitionsRDD[2] at map at Spark03_RDD_Persist.scala:19 []
    |       CachedPartitions: 12; MemorySize: 720.0 B; ExternalBlockStoreSize: 0.0 B; DiskSize: 0.0 B
    |   ReliableCheckpointRDD[4] at collect at Spark03_RDD_Persist.scala:36 []

6.3  缓存和检查点区别

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

        Spark目前支持Hash分区和Range分区,和用户自定义分区。Hash分区为当前的默认分区。分区器直接决定了RDD中分区的个数、RDD中每条数据经过Shuffle后进入哪个分区,进而决定了Reduce的个数。

  • 只有Key-Value类型的RDD才有分区器,非Key-Value类型的RDD分区的值是None
  • 每个RDD的分区ID范围:0~(numPartitions - 1),决定这个值是属于那个分区的。
  • 8、总结

RDD概念

        弹性分布式数据集,是Spark中最基本的数据处理模型

RDD核心属性:

        分区列表

        分区计算函数

        RDD之间的依赖关系

        分区器(可选)

        首选位置(可选)

RDD任务执行:

序列化:   

        算子外的代码,Driver端执行,算子内的代码,Executor中执行,算中引用的外部变量,通过闭包检测机制到Task中

依赖关系:

        相邻的两个RDD之间的关系,称之为依赖关系,多个连续的依赖关系称之为血缘关系

宽依赖(ShuffleDependency):

        宽依赖表示同一个父(上游)RDD的Partition被多个子(下游)RDD的Partition依赖,会引起Shuffle,总结:宽依赖我们形象的比喻为多生

窄依赖(NarrowDependency):

        窄依赖表示每一个父(上游)RDD的Partition最多被子(下游)RDD的一个Partition使用,窄依赖我们形象的比喻为独生子女

阶段划分:

        阶段的数量等于shuffle操作的个数+ 1

        任务的数量应该是所有的阶段的最后一个RDD的分区数之和

        action - > runjob -> clean(func) -> dagScheduler.runJob -> submitJob ->  createResultStage ->  getOrCreateShuffleMapStage ->  submitMissingTasks 

         ShuffleDependency >>>>>>  ShuffleMapStage

        ShuffleMapStage   >>>>>>   ShuffleMapTask

         ResultStage   >>>>>>>   ResultTask

       ShuffleMapTask  >>>>> shuffleWriterProcessor  

持久化:

        Cache缓存只是将数据保存起来(内存、磁盘),不切断血缘依赖。

        Checkpoint(磁盘)检查点切断血缘依赖。

        Checkpoint 需要和cache 一起使用,Checkpoint 为了安全会重新跑

分区:

        Hash分区、Range分区、自定义分区

         

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值