一.RDD的概述
1.1. 什么是RDD
RDD(Resilient Distributed Dataset)叫做弹性分布式数据集,是Spark中最基本的数据抽象,它代表一个不可变、可分区、里面的元素可并行计算的集合。RDD具有数据流模型的特点:自动容错、位置感知性调度和可伸缩性。RDD允许用户在执行多个查询时显式地将工作集缓存在内存中,避免了如mapreduce许多重复的落地写磁盘操作,也避免了网络间传输数据时过多的序列化与反序列化操作开销,后续的查询能够重用工作集,这极大地提升了查询速度。
1.2. RDD的特性
- RDD是由一系列的partition组成
- 高效的容错性
- 算子是作用在partition上的
- RDD之间有依赖关系
- 分区器是作用在k,v格式的RDD上
- partition对外提供最佳的计算位置
问题:
1.什么是k,v格式的RDD?
RDD中的每个元素是一个二元组,那么这个RDD就是k,v格式的RDD,这种类型的RDD是根据哈希来分区的,类似于mapreduce当中的partition接口。
2.spark中能直接读取HDFS中文件吗?
spark中没有能直接读取HDFS中文件的方法,比如sc.textFile()调用的是MR来读取,首先会split,就是一个block,每个block对应RDD中的一个partition。
3.哪里体现了RDD的弹性?
- RDD的partition个数可多可少
- RDD之间有依赖关系
4.哪里体现了RDD的分布式?
RDD的分区分布在多个节点上。
5.如何理解高效的容错性?
RDD血缘关系、重新计算丢失分区、无需回滚系统、重算过程在不同节点之间并行、只记录粗粒度的操作
1.3. wordcount图解RDD
其中hello.txt
二.RDD宽依赖与窄依赖
2.1 RDD依赖关系的本质内幕
由于RDD是粗粒度的操作数据集,每个Transformation操作都会生成一个新的RDD,所以RDD之间就会形成类似流水线的前后依赖关系;RDD和它依赖的父RDD(s)的关系有两种不同的类型,即窄依赖(narrow dependency)和宽依赖(wide dependency)。如图所示显示了RDD之间的依赖关系。
从图中可知:
窄依赖:是指每个父RDD的一个Partition最多被子RDD的一个Partition所使用,例如map、filter、union等操作都会产生窄依赖;(独生子女)
宽依赖:是指一个父RDD的Partition会被多个子RDD的Partition所使用,例如groupByKey、reduceByKey、sortByKey等操作都会产生宽依赖;(超生)
2.2 依赖关系下的数据流视图
spark之所以将依赖分为narrow与shuffle,基于两点原因:
首先,narrow dependencies可以支持在同一个cluster node上以pipeline的形式执行多条命令,例如在执行了map后,紧接着执行filter。相反shuffle dependencies需要所有的父分区都是可用的。
其次,则是从失败回复的角度考虑。窄依赖的失败恢复更有效,因为它只需从新计算父分区即可,而且可以并行在不同节点进行重计算。而宽依赖牵涉RDD各级的多个父分区。
如下图所示:
三.RDD中算子(这里只作简要概述)
3.1 什么是算子
算子是RDD中定义的函数,可以对RDD中的数据进行转换和操作。
算子分类:
- Transformation(转换)
- Action(执行)
作用: - transformation是得到一个新的RDD,方式很多,比如从数据源生成一个新的RDD,从RDD生成一个新的RDD,Transformation属于延迟计算,当一个RDD转换成另一个RDD时并没有立即进行转换,仅仅是记住了数据集的逻辑操作。
- action是得到一个值,或者一个结果(直接将RDDcache到内存中),触发Spark作业的运行,真正触发转换算子的计算。
所有的transformation都是采用的懒策略,如果只是将transformation提交是不会执行计算的,计算只有在action被提交的时候才被触发。
3.2 实例操作
以下实例用来验证action和transformation之间区别与联系(RDD.cache()也属于懒执行持久化算子):
package cn.itcast.demo
import org.apache.spark.{SparkConf, SparkContext}
class testdemo {
}
object testdemo {
/*
*持久化算子
cache默认将数据存在内存中,懒执行算子
*/
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local").setAppName("cacheTest")
val sc = new SparkContext(conf)
val RDD = sc.textFile("datademo.txt")
RDD.cache()
val starttime = System.currentTimeMillis()
val count1 = RDD.count()
val endtime = System.currentTimeMillis()
println("count1前后时间差为:" + (endtime - starttime))
val starttime1 = System.currentTimeMillis()
val count2 = RDD.count()
val endtime1 = System.currentTimeMillis()
println("count2前后时间差为:" + (endtime1 - starttime1))
/*
在这里,count1肯定是从磁盘读取数据,因为RDD不存数据
RDD.cache()也并没有将数据放在内存中(懒执行)
接下来count()也就不能从内存中读取数据,但作为action它会触发持久化算子
也就是cache,于是再接下来的count2就能得到被cache保存在内存中的数据了
*/
sc.stop()
}
}
参考:http://www.cnblogs.com/qingyunzong/p/8899715.html
https://www.cnblogs.com/LgyBean/p/6251359.html
图1~4来源:http://www.cnblogs.com/qingyunzong/p/8899715.html