这是我参与11月更文挑战的第14天,活动详情查看:2021最后一次更文挑战
一、概述
RDD 只支持粗粒度转换, 即在大量记录上执行的单个操作。将创建 RDD 的一系列 Lineage (血统)记录下来, 以便恢复丢失的分区。
RDD的Lineage会记录RDD的元数据信息和转换行为, 当该RDD的部分分区数据丢失时, 可根据这些信息来重新运算和恢复丢失的数据分区。
如图:
RDD 和 它依赖的 父RDD(s) 的关系有两种不同的类型, 即窄依赖( narrow dependency) 和 宽依赖(wide dependency)。
依赖有2个作用:
- 用来解决数据容错
- 用来划分
stage。
窄依赖:
1:1或n:1宽依赖:n:m; 意味着有shuffle
窄依赖和宽依赖如图:
DAG (Directed Acyclic Graph) 有向无环图。原始的 RDD 通过一系列的转换就就形成了 DAG, 根据 RDD 之间的依赖关系的不同将 DAG 划分成不同的 Stage:
- 对于窄依赖,
partition的转换处理在Stage中完成计算 - 对于宽依赖, 由于有
Shuffle的存在, 只能在parent RDD处理完成后, 才能开始接下来的计算,宽依赖是划分Stage的依据
RDD 任务切分中间分为: Driver programe、Job、Stage(TaskSet) 和 Task
Driver program: 初始化一个SparkContext即生成一个Spark应用Job: 一个Action算子就会生成一个JobStage: 根据RDD之间的依赖关系的不同将Job划分成不同的Stage, 遇到一个宽依赖则划分一个StageTask:Stage是一个TaskSet, 将Stage划分的结果发送到不同的Executor执行即为一个TaskTask是Spark中任务调度的最小单位; 每个Stage包含许多Task, 这些Task执行的计算逻辑相同的, 计算的数据是不同的
注意:
Driver programe->Job->Stage->Task每一层都是 1对n 的关系。
// 窄依赖
val rdd1 = sc.parallelize(1 to 10, 1)
val rdd2 = sc.parallelize(11 to 20, 1)
val rdd3 = rdd1.union(rdd2)
rdd3.dependencies.size
rdd3.dependencies
// 打印rdd1的数据
rdd3.dependencies(0).rdd.collect
// 打印rdd2的数据
rdd3.dependencies(1).rdd.collect
// 宽依赖
val random = new scala.util.Random
val arr = (1 to 100).map(idx => random.nextInt(100))
val rdd1 = sc.makeRDD(arr).map((_, 1))
val rdd2 = rdd1.reduceByKey(_+_)
// 观察依赖
rdd2.dependencies
rdd2.dependencies(0).rdd.collect
rdd2.dependencies(0).rdd.dependencies(0).rdd.collect
二、案例:WordCount
val rdd1 = sc.textFile("/wcinput/wc.txt")
val rdd2 = rdd1.flatMap(_.split("\\s+"))
val rdd3 = rdd2.map((_, 1))
val rdd4 = rdd3.reduceByKey(_+_)
val rdd5 = rdd4.sortByKey()
rdd5.count
// 查看RDD的血缘关系
rdd1.toDebugString
rdd5.toDebugString
// 查看依赖
rdd1.dependencies
rdd1.dependencies(0).rdd
rdd5.dependencies
rdd5.dependencies(0).rdd
// 查看最佳优先位置
val hadoopRDD = rdd1.dependencies(0).rdd
hadoopRDD.preferredLocations(hadoopRDD.partitions(0))
# 使用 hdfs 命令检查文件情况
hdfs fsck /wcinput/wc.txt -files -blocks -locations