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分区、自定义分区