目录
一、RDD
Spark 中最基本的数据抽象是 RDD。
RDD:弹性分布式数据集 (Resilient Distributed DataSet)。
1、五大特性
-
RDD 是有一系列的 partition 组成
-
函数作用在每个 partition 上
-
RDD 有一系列的依赖。
-
可选项 : 如果 rdd 是 k-v 对形式的,可以传一个 partition重新分区;Shuffle patition 中数据通过重分区到其他的 partition 中 这个过程叫做shuffle (groupBykey 和reduceBykey 的效率比较,在shuff阶段提高效率 ( 文章链接: https://blog.csdn.net/qq_44472134/article/details/104339087 )
5、可选项
:RDD可以将数据加载到更好的节点上
可选项的意思是还有符合条件的时候才会执行
2、RDD 有三个基本特性
这三个特性分别为:分区,不可变,并行操作。
a, 分区 Partition
每一个 RDD 包含的数据被存储在系统的不同节点上。逻辑上我们可以将 RDD 理解成一个大的数组,数组中的每个元素就代表一个分区 (Partition) 。
在物理存储中,每个分区指向一个存储在内存或者硬盘中的数据块 (Block) ,其实这个数据块就是每个 task 计算出的数据块,它们可以分布在不同的节点上。
所以,RDD 只是抽象意义的数据集合,分区内部并不会存储具体的数据,只会存储它在该 RDD 中的 index,通过该 RDD 的 ID 和分区的 index 可以唯一确定对应数据块的编号,然后通过底层存储层的接口提取到数据进行处理。
在集群中,各个节点上的数据块会尽可能的存储在内存中,只有当内存没有空间时才会放入硬盘存储,这样可以最大化的减少硬盘 IO 的开销。
b,不可变
不可变性是指每个 RDD 都是只读的,它所包含的分区信息是不可变的。由于已有的 RDD 是不可变的,所以我们只有对现有的 RDD 进行转化 (Transformation) 操作,才能得到新的 RDD ,一步一步的计算出我们想要的结果。
这样会带来这样的好处:我们在 RDD 的计算过程中,不需要立刻去存储计算出的数据本身,我们只要记录每个 RDD 是经过哪些转化操作得来的,即:
依赖关系,这样一方面可以提高计算效率,一方面是错误恢复会更加容易。如果在计算过程中,第 N 步输出的 RDD 的节点发生故障,数据丢失,那么可以根据依赖关系从第 N-1 步去重新计算出该 RDD,这也是 RDD 叫做
"弹性"分布式数据集的一个原因。
c,并行操作
因为 RDD 的分区特性,所以其天然支持并行处理的特性。即不同节点上的数据可以分别被处理,然后生成一个新的 RDD。
3、RDD 的结构
每个 RDD 里都会包括分区信息、依赖关系等等的信息,如下图所示:
![](https://i-blog.csdnimg.cn/blog_migrate/6008c4ee07f7a1c6a8a38bc50124b277.png)
RDD图解
a,Partitions
Partitions 就是上面所说的,代表着 RDD 中数据的逻辑结构,每个 Partion 会映射到某个节点内存或者硬盘的一个数据块。
b,SparkContext
SparkContext 是所有 Spark 功能的入口,代表了与 Spark 节点的连接,可以用来创建 RDD 对象以及在节点中的广播变量等等。一个线程只有一个 SparkContext。
c,SparkConf
SparkConf 是一些配置信息。
d,Partitioner
Partitioner 决定了 RDD 的分区方式,目前两种主流的分区方式:Hash partioner 和 Range partitioner。Hash 就是对数据的 Key 进行散列分布,Rang 是按照 Key 的排序进行的分区。也可以自定义 Partitioner。
e,Dependencies
Dependencies 也就是依赖关系,记录了该 RDD 的计算过程,也就是说这个 RDD 是通过哪个 RDD 经过怎么样的转化操作得到的。
这里有个概念,根据每个 RDD 的分区计算后生成的新的 RDD 的分区的对应关系,可以分成窄依赖和宽依赖。
窄依赖就是父 RDD 的分区可以一一对应到子 RDD 的分区,如图:
![](https://i-blog.csdnimg.cn/blog_migrate/43275805b41fa549ab38f530beaa5d4a.png)
由于窄依赖的特性,窄依赖允许子 RDD 的每个分区可以被并行处理产生,而且支持在同一个节点上链式执行多条指令,无需等待其
它父 RDD 的分区操作。
宽依赖是说父 RDD 的每个分区可以被多个子 RDD 分区使用。
![](https://i-blog.csdnimg.cn/blog_migrate/f275592a02f4b76e17ead222206469a8.png)
Spark 区分宽窄依赖的原因主要有两点:
-
窄依赖支持在同一节点上进行链式操作,比如在执行了 map 后,紧接着执行 filter 操作。相反,宽依赖需要所有父分区都是可用的。
-
从失败恢复的角度考虑,窄依赖失败恢复更有效,因为只要重新计算丢失的父分区即可,而宽依赖涉及到 RDD 的各级多个父分区。
f,Checkpoint
检查点机制,在计算过程中有一些比较耗时的 RDD,我们可以将它缓存到硬盘或者 HDFS 中,标记这个 RDD 有被检查点处理过,并且清空它的所有依赖关系。同时,给它新建一个依赖于 CheckpointRDD 的依赖关系,CheckpintRDD 可以用来从 硬盘中读取 RDD 和生成新的分区信息。
这么做之后,当某个 RDD 需要错误恢复时,回溯到该 RDD,发现它被检查点记录过,就可以直接去硬盘读取该 RDD,无需重新计算。
g,Preferred Location
针对每一个分片,都会选择一个最优的位置来计算,数据不动,代码动。
h,Storage Level
用来记录 RDD 持久化时存储的级别,常用的有:
-
MEMORY_ONLY:只存在缓存中,如果内存不够,则不缓存剩余的部分。这是 RDD 默认的存储级别。
-
MEMORY_AND_DISK:缓存在内存中,不够则缓存至内存。
-
DISK_ONLY:只存硬盘。
-
MEMORY_ONLY_2 和 MEMORY_AND_DISK_2等:与上面的级别和功能相同,只不过每个分区在集群两个节点上建立副本。
i,Iterator
迭代函数和计算函数是用来表示 RDD 怎样通过父 RDD 计算得到的。
迭代函数首先会判断缓存中是否有想要计算的 RDD,如果有就直接读取,如果没有就查找想要计算的 RDD 是否被检查点处理过。如果有,就直接读取,如果没有,就调用计算函数向上递归,查找父 RDD 进行计算。
二、RDD的API操作
# 从hdfs上获取数据
val ers =sc.textFile("hdfs://node132:9000/root/wordcount.txt")
# 从本地获取文件
val res0 = sc.textFile("C:\\Users\\Undo\\Desktop\\spark.txt")
# SparkRDD的wordcount求和
//******************************************每个RDD都需要的部分
val conf = new SparkConf()
.setAppName("w") //起的job名字
.setMaster("local[2]") //启动两个线程工作
val sc = new SparkContext(conf)
//***********************************************************
val s=sc.textFile("文件地址(可以是本地也可以是hdfs)").flatMap(x=>x.split("")).map((_,1)).reduceByKey(_+_).sortBy(_._2,false);
println(s.collect().toBuffer) //每个RDD需要转换之后才能输出