Spark 总结之RDD(一)
1. 背景
- Spark作为大数据分析处理引擎,参考了大量MapReduce的设计思路,但也在mapreduce基础上做了更多抽象,这样的接口设计使得spark编程更加便利
- Spark抽象出来的有RDD DataSet DataFrame DStream等.
- Spark作为分布式数据处理引擎,本质还是解决2个问题,数据切分和代码逻辑切分. 数据切分,这样可以使得分布式集群中每台计算机都处理一部分数据,代码逻辑切分,使得分布式集群中每台计算机领取各自的处理任务,并行执行任务.
- RDD作为Spark抽象出来的算子,内部就隐含了这2步最关键的逻辑和流程.
2. RDD
2.1 概述
- RDD全程是Resilient Distributed DataSet,是一个弹性可复原的分布式数据集,是Spark中最基本的抽象.
- RDD本身不可变, 内部包含多个分区,可以并行计算的集合.
- RDD本身并不保存需要处理的数据,但会保存所需要处理的数据以及对应的数据处理逻辑.包括数据来源,调用什么代码,传入的函数,依赖关系等.
- 一个RDD的输出可能是另外一个RDD的输入
2.2 特点
- RDD有一系列连续的分区,分区编号从0开始,分区数量决定了对应阶段task的并行度
- RDD中每个输入切片都会有对应处理代码逻辑,很多时候是以函数形式体现.每一个分区都会生成一个task,
- RDD和RDD之间存在一些依赖关系,例如RDD调用transformation方法之后,会生成一个新的RDD,子RDD会记录父RDD的依赖关系,包括宽依赖和窄依赖.
注意,宽窄依赖是指对分区数量而言.依赖是指父子RDD之间的依赖
宽依赖就是父RDD有多个分区,子依赖分区数量降低或者降低为1.一般宽依赖都是发生了shuffle
窄依赖就是父子RDD的分区都是1,一般窄依赖是没有发生shuffle
- KV类型的RDD在shuffle时,会有分区器,默认是HashPartitioner,当然还可以有其他分区器.
注意,Spark也有shuffle,和mapreduce一样.同样spark的shuffle也需要解决数据倾斜问题.解决思路也是如增加分区数量 加内存 key打散 提前聚合(分区或者map端聚合都可以)等等
- 如果从HDFS中读取数据,会有一个最有位置.因为最好的数据和代码布置方式就是代码需要读取的数据就在运行代码的存储节点上,这样数据不需要跨网络通信.
移动计算要比移动数据更好,不过如果遇到负载均衡问题,有时候也会采用移动数据来匹配低负载的计算节点.
3. Transformation和action RDD
3.1 概述
- 顾名思义,Tasnformation就是转换RDD算子,只是起到数据转换的作用.类比一下,mapreduce的主要思想就是分2步,第一步map阶段,将数据转换为key value形式数据.reduce阶段,针对map阶段结果做聚合
- RDD的行动算子action RDD,顾名思义,就是会触发行为的算子.从整体设计而言,一般都是等到所有代码逻辑确认后,才好去优化代码.Spark采取DAG形式来对输入的代码逻辑做设计,并优化.所以之后触发行动算子之后,整体的代码逻辑才好做一体化设计与优化.
举个例子,接收到产品需求,一开始说关闭功能A,过一小时说打开功能A,又过一小时说关闭功能A.从代码角度来说,肯定是一次性接受完所有需求,再实施要好很多,否则就会出现重复性劳动和计算
- Transformation算子是懒加载的,只有等到行动算子触发之后,代码才会真正执行.在此之前,都只会记录数据,记录要对数据做的操作,而不会触发代码执行.
- 只有action 算子才能触发代码执行
3.2 演示
使用spark shell演示
- 这里故意写了一个错误的hdfs协议,不是完整的hdfs,而是hd,但执行代码的时候可以看到,shell并没有报错.因为只有真正触发action算子,才会报错
val lines = sc.textFile("hd://linux100:8020/spark/b.txt")

val words = lines.flatMap(_.split("\\s+"))

val wordAndOne = words.map(e=>(e, 1))

- 注意,reduceByKey并不是action算子,只是因为需要做分区聚合,所以会和namenode通信,确认分区数量,文件信息等.所以这里会有检查
val res = wordAndOne.reduceByKey


4. RDD和RDD分区
4.1 正常情况下RDD分区
- 如果是从HDFS读取数据, RDD分区决定于输入的文件分区数.
演示1(spark shel中演示):
- 制造数据(相同文件,复制多份)

- 读取文件并查看分区数
val lines = sc.textFile("hdfs://linux100:8020/spark/wc")
lines.partitions.length

从上述可以看出,如果是3个体积相差不大的小文件,则文件数量就是分区数量.
演示2:
- 制造数据
val list = List(1,2,3,4,5,6,7,8,9)
val rdd1 = sc.parallelize(list)
rdd1.partitions.length


上述可以看出,如果不指定分区数量,则分区数就是默认的虚拟核心数. 因为2个虚拟核心,刚好可以并行完成2个task,而一个task刚好处理一个分区,这样效果和性能最好.
2. 如果创建RDD时手动指定了分区数,则分区就是指定的分区数量
3. 如果没有指定RDD分区数量,文件分区数如果是1(例如一个小于128MB的小文件,一个小数组转换而成的RDD等),则默认的分区数是设置的虚拟核心数


4.2 特殊情况下RDD分区
4.2.1 概述
- 如果处理一批文件,文件之间大小有较大差异,为了防止数据倾斜,RDD分区会将大文件拆分为多个分区进行处理,防止数据倾斜.
- 但其实对于海量数据处理而言,默认的hdfs的128MB,小于128MB的文件,就算大小有差异,但都控制在128MB的粒度内,有时候可以忽略这种数据量差异,直接指定文件相对大文件分区为1
4.2.2 演示

scala> val lines = sc.textFile("hdfs://linux100:8020/spark/wordcount")
lines: org.apache.spark.rdd.RDD[String] = hdfs://linux100:8020/spark/wordcount MapPartitionsRDD[12

本文是Spark总结的第一部分,主要探讨Resilient Distributed Dataset (RDD)。RDD是Spark的基本数据抽象,不可变且包含多个分区,支持并行计算。RDD的特点包括分区编号、数据处理逻辑和依赖关系。转型操作是懒加载的,只有在触发行动操作时才会执行。RDD分区通常基于输入文件的分区数或手动指定,Spark会根据文件大小调整分区以避免数据倾斜。
最低0.47元/天 解锁文章
3228

被折叠的 条评论
为什么被折叠?



