Spark学习笔记之RDD 1

概要

花了快两周时间单核并发双线程处理Scala和Spark,二者有很多相似之处,结果学着学着发现可以直接学Spark了,于是直到RDD数据结构学完,现在做个记录。

什么是RDD

首先,要明白对于Scala而言有自己的数据类型以及如何定义变量,那么在Spark里,我们处理数据集的时候,就需要一种既能存储数据,又能适配很多函数,可以快速对数据进行操作的一种数据结构,这个时候RDD就诞生了。

RDD(Resilient Distributed Dataset)叫做分布式数据集,是Spark 中最基本的数据抽象,它代表一个不可变、可分区、里面的元素可并行计算的集合。在Spark 中,对数据的所有操作不外乎创建RDD、转化已有RDD 以及调用RDD 操作进行求值。每个RDD 都被分为多个分区,这些分区运行在集群中的不同节点上。RDD 可以包含Python、Java、Scala 中任意类型的对象, 甚至可以包含用户自定义的对象。RDD 具有数据流模型的特点:自动容错、位置感知性调度和可伸缩性。RDD 允许用户在执行多个查询时显式地将工作集缓存在内存中,后续的查询能够重用工作集,这极大地提升了查询速度。RDD 支持两种操作:transformation操作和action操作。RDD 的转化操作是返回一个新的RDD 的操作,比如map()和filter(),而action操作则是向驱动器程序返回结果或把结果写入外部系统的操作。比如count() 和first()。

上面是百度的一段来自RDD的概念介绍,当然如果不实践的话很难理解RDD这种数据结构,我感觉介绍的很全能但是很抽象。那么我根据RDD的几大特性分别举例就比较好理解啦~

1) 分区(Partition),即数据集的基本组成单位。

由于刚看完大部分Hadoop的课程,对分区这个概念一点都不陌生,当时hadoop里有干活的概念:一个区数据对应一个处理它的ReduceTask,而MapTask的数量取决于拿到数据进行的切片操作,切片和分区是两个动作。而在Spark里 切片和分区合并了,这里就要提到Spark是如何进行分布式数据处理的:

在数据读入阶段,例如使用sc.textFile读取一个文件数据生成RDD,就可以指定RDD的分区,即理解为切片操作,每个区里存的数据不一样,并且后续使用算子编写的逻辑处理打包成Task来处理不同区的数据时,RDD被划分为多少个分区就会需要多少初始Task。

Task是一系列的算子编写而成的,部分算子具备shuffle操作,即会将分区原本存储的数据重新排列,例如groupby,reducebykey等算子,重新排列聚合后的RDD的partition数目跟具体操作有关,可以是和之前分区数量一样,也可以增加/减少分区,例如repartition算子允许指定分区数。

RDD在计算的时候,每个分区都会起一个task,所以rdd的分区数目决定了总的task数目。因为RDD的算子打包成的Task是在Executor节点上进行的,申请的计算节点(Executor)数目和每个计算节点核数,决定了你同一时刻可以并行执行的task,例如:

一个RDD有100个分区,那么计算的时候就会生成100个task,设置task间并行的参数是conf spark.sql.shuffle.partitions=100,理想状态是每个区的数据均会被相同的操作逻辑打包的Task同时执行,即100个相同功能的Task同一时间一起处理100个分区。

如果你的服务器配置为10个计算节点(执行器excutor)  --num-executors 10    每个2个核,executor-cores 2   一般 2~4 为宜,默认为2。

Task被执行的并发度 = Executor数目 * 每个Executor核数(=core总个数)这意味着同一时间哪怕你服务器性能拉满,10个Executor火力全开也只能运转20个Task,即只能处理20个分区,那么想要把所有分区的数据处理完就需要并行5个循环,即1单位时间20个Task并行处理20个分区,执行5单位时间。计算这个RDD就需要5个循环。

如果计算资源不变,你有101个task的话,就需要6个循环,在最后一轮中,只有一个task在执行,其余核都在空转。

如果资源不变,你的RDD只有2个分区,那么同一时刻只需要2个task,那么你仍然启动20个Task,其中2个真正运行处理数据,其余18个核空转,造成资源浪费。

这就是在spark调优中,根据服务器资源配置需要适当增大RDD分区数目,平摊每个分区的数据量,增大任务并行度的原因。


2) 每个分区的逻辑计算---算子。
首先当我们从文件里拿到数据/内存里生成数据 ->转化成RDD之后,就可以使用算子对数据进行操作,例如map/flatmap/filter等算子,这些算子并不会改变初始分区里的数据内容,它们的作用仅仅是对RDD里的每个数据元素进行操作,属于迭代类型的算子,类似Python里的lambda函数,当然这些算子里允许传入函数做型参,通常都使用匿名函数的格式,例如var rdd1 = rdd0.map(x=>{x+1}).即把rdd0里的每个数据元素➕1,当然这里rdd0里的元素类型是Int哈。有些算子具备shuffle性质,就会改变初始分区里的数据内容,例如groupby算子,就会对数据进行分组处理,那么再生成的rdd里分区数量不会改变但是每个区里的数据内容会变成同一组的数据。


3) RDD 之间的依赖关系。
RDD 的每次转换都会生成一个新的RDD,所以RDD 之间就会形成类似于流水线一样的前后依赖关系。在部分分区数据丢失时,Spark 可以通过这个依赖关系重新计算丢失的分区数据。

但是有Spark 采用惰性计算模式,即不保存数据,只保存计算逻辑,说白了就是只记第一步xxx,第二步xxx,第三步xxx...,数据是从文件里一行一行或一个字节一个字节读进来的,当然也有缓存算子,允许一个分区一个分区的进行数据逻辑计算。默认情况下,Spark的RDD 会在你每次对它们进行行动操作时重新计算,不存在某个中间过程的RDD复用的说法,例如rdd0是原始数据,rdd1进行map操作,rdd2进行reduce操作,此时想拿rdd1去进行groupby操作得到rdd3是不行的,只会从rdd0->rdd1->rdd3。如果想在多个行动操作中重用同一个RDD , 可以使用RDD的持久化功能让Spark 把这个RDD 缓存下来,即rdd.cache()配合rdd.checkpoint(),cache可以把数据保存在内存里,checkpoint将cache保存的数据从内存上写进磁盘生成数据文件,相当于修改了数据源。


4) Partitioner, 即RDD的分片函数(分区器)。
当前Spark 中实现了两种类型的分片函数,一个是基于哈希的HashPartitioner,另外一个是基于范围的RangePartitioner。只有对于于key-value 的RDD , 才会有Partitioner, 非key-value 的RDD 的Parititioner 的值是None。Partitioner 函数不但决定了RDD 本身的分片数量, 也决定了parent RDD Shuffle 输出时的分片数量。

当然分区器是支持自定义的,只要继承partition接口重写里面的两个方法即可。

5) 一个列表, 存储存取每个Partition 的优先位置( preferred location)。
对于一个HDFS 文件来说,这个列表保存的就是每个Partition 所在的块的位置。按照“移动数据不如移动计算”的理念,Spark 在进行任务调度的时候,会尽可能地将计算任务分配到其所要处理数据块的存储位置。

  • 18
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值