一、要想更深一度的了解Spark RDD,首先我们得初步了解一下Spark RDD,那么问题就来了,什么是RDD呢?
RDD( resilient distributed dataset ) 弹性分布式数据集;RDD代表是一个不可变的、可分区的、支持并行计算的元素集合(类似于Scala中的不可变集合),RDD可以通过HDFS、Scala集合、RDD转换、外部的数据集(支持InputFormat)获得;并且我们可以通知Spark将RDD持久化在内存中,可以非常高效的重复利用或者在某些计算节点故障时自动数据恢复;
二、了解完RDD之后,我们来分析一下它的原理
RDD依赖——lineage(血统 | 血缘)
在对数据源RDD应用转换操作时,产生的新的RDD会有一种依赖关系称为血统(Lineage); Spark应用在计算时会根据血统(lineage)逆向推导出所有Stage(阶段),每一个Stage的分区数量决定了任务的并行度,一个Stage实现任务的本地计算(大数据计算时网络传输时比较耗时的)
object WordCountApplication {
def main(args: Array[String]): Unit = {
// spark word count应用
val conf = new SparkConf().setMaster("local[*]").setAppName("wordcount")
val sc = new SparkContext(conf)
// rdd由两个分区构成
val rdd = sc.makeRDD(List("Hello Kafka", "Hello Scala", "Hello Hadoop"), 2) // rdd1
rdd
.flatMap(_.split("\\s")) // rdd1 --> rdd2
.map((_, 1L)) // rdd2 --> rdd3
.groupByKey(3) // rdd3 --> rdd4
.map(t2 => (t2._1, t2._2.size)) // rdd4 --> rdd5
.sortBy(_._2, false, 2) //...
.foreach(t2 => println(t2._1 + "\t" + t2._2))
sc.stop()
}
}
宽窄依赖
RDD之间血缘关系可以详细分为两种:宽依赖(Wide Dependency)和窄依赖(Narrow Dependency);
- 窄依赖(Narrow Dependency): 父RDD的一个分区对应一个子RDD的分区(1:1)或者多个父RDD的分区对应一个子RDD的分区(N:1);
- 宽依赖(Wide Dependency): 父RDD的一个分区对应多个子RDD的分区(1:N);
- 注意:宽、窄依赖和Spark应用的Stage划分有极为紧密关系;
我们发现:Spark在拆分阶段时,发现宽依赖立即划分Stage,如果发现窄依赖归属于同一个Stage
RDD的容错
Spark计算时当某些Task在计算时未正确处理,则会触发RDD的容错机制;容错机制分为三种情况:
- 重新计算(默认):如果只有窄依赖只需要重新计算故障Task数据分区即可;如果既有宽依赖又有窄依赖,则需要重新计算故障任务前的所有分区的数据;
- RDD持久化:将某一个RDD持久化内存或者磁盘中;当Task故障时,直接从持久化RDD中恢复数据即可;
- Checkpoint(检查点): 可以对某一个RDD设定检查点,RDD的数据信息则会持久化到一个共享的分布式文件系统中(HDFS),当检查点设定结束后,整个血统的链路会中断,检查点机制比较适合血统链路较长Spark应用,建议设置给宽依赖RDD;
RDD持久化(persist)
// 持久化方法 MEMORY_ONLY
rdd.persist()
// 缓存方法 MEMORY_ONLY
rdd.cache()
// 设定持久化级别
rdd.persist(StorageLevel.MEMORY_AND_DISK)
// 取消RDD持久化方法
rdd.unpersist()
注意:
- 对RDD进行持久化时可以选择内存、磁盘、内存+磁盘这样的存储介质,如果Spark集群的内存空间足够大,推荐使用
MEMORY_ONLY
MEMORY_ONLY_2
中的_2
表示除过增加数据副本MEMORY_ONLY_SER
中的_SER
表示将RDD中的数据序列化后存储
检查点机制(checkpoint)
// 设定检查点数据的存放目录
sc.setCheckpointDir("hdfs://SparkOnStandalone:9000/checkpoint")
// rdd由两个分区构成
val rdd = sc.makeRDD(List("Hello Kafka", "Hello Scala", "Hello Hadoop"), 2)
val groupRdd = rdd
.flatMap(_.split("\\s"))
.map((_, 1L))
.groupByKey(3)
// 标记检查点
groupRdd.checkpoint()
groupRdd.map(t2 => (t2._1, t2._2.size))
.sortBy(_._2, false, 2)
.foreach(t2 => println(t2._1 + "\t" + t2._2))
sc.stop()