Spark核心RDD及相关算子

目录

1、 RDD是什么

2、 RDD的五大属性

2.1、RDD是由一系列的partition组成的。

2.2、函数是作用在每一个partition(split)上的。

2.3、RDD之间有一系列的依赖关系。

2.4、分区器是作用在K,V格式的RDD上。

2.5、RDD提供一系列最佳的计算位置。

3、RDD图及相关理解

 4、RDD的创建方式(共三种)

4.1、通过已经存在的scala集合去构建(少用)

4.2、加载外部的数据源去构建

4.3、从已经存在的rdd进行转换生成一个新的rdd

5、RDD的算子分类

5.1、transformation转换算子

5.2、action 行动算子

5.3、持久化算子(也叫缓存机制,详情参看8/9/10章节)

6、RDD的依赖关系

6.1、窄依赖

6.2、宽依赖

7、lineage(血统)

8、RDD的缓存机制

8.1、如何对rdd设置缓存

8.2、什么时候设置缓存

8.3、清除缓存数据

9、RDD的checkpoint机制

9.1、如何设置checkpoint

9.2、checkpoint 的执行原理:

10、cache ,persist ,checkpoint 区别

10.1、cache 与persist:

10.2、checkpoint:

10.3、persist(StorageLevel.DISK_ONLY) 与 checkpoint:

11、DAG有向无环图

11.1、DAG划分stage

11.2、为什么要划分stage

11.3、如何划分stage

11.4、stage与stage之间的关系

11.5、stage计算模式



1、 RDD是什么

RDD(Resilient Distributed Dataset)叫做弹性分布式数据集,是Spark中最基本的数据抽象,它代表一个不可变可分区、里面的元素可并行计算的集合。

  • Dataset: 就是一个集合,存储很多数据。
  • Distributed:它内部的元素进行了分布式存储,方便于后期进行分布式计算。
  • Resilient: 表示弹性,rdd的数据是可以保存在内存或者是磁盘中。

2、 RDD的五大属性

2.1、RDD是由一系列的partition组成的。

数据集的基本组成单位;spark中任务是以task线程的方式运行, 一个分区就对应一个task线程。

用户可以在创建RDD时指定RDD的分区个数,如果没有指定,那么就会采用默认值(默认值2)。

2.2、函数是作用在每一个partition(split)上的。

2.3、RDD之间有一系列的依赖关系。

spark任务的容错机制就是根据这个特性(血统)而来。

2.4、分区器是作用在K,V格式的RDD上。

当前Spark中实现了两种类型的分区函数,一个是基于哈希的HashPartitioner,(key.hashcode % 分区数= 分区号);
另外一个是基于范围的RangePartitioner。
只有对于key-value的RDD(RDD[(String, Int)]),并且产生shuffle,才会有Partitioner;
非key-value的RDD(RDD[String])的Parititioner的值是None。

2.5、RDD提供一系列最佳的计算位置。

涉及到数据的本地性,数据块位置最优。
spark任务在调度的时候会优先考虑对存储有数据的节点开启计算任务减少数据的网络传输,提升计算效率。

3、RDD图及相关理解

(1)textFile方法底层封装的是MR读取文件的方式,读取文件之前先split,默认split大小是一个block大小

(2)RDD实际上不存储数据;partition(存的是计算逻辑)也是不存数据的;

(3)什么是K,V格式的RDD?

如果RDD里面存储的数据都是二元组对象,那么这个RDD我们就叫做K,V格式的RDD。

(4)哪里体现RDD的弹性(容错)?

partition数量,大小没有限制,体现了RDD的弹性。

RDD之间依赖关系,可以基于上一个RDD重新计算出RDD。

(5)哪里体现RDD的分布式?

RDD是由Partition组成,partition是分布在不同节点上的。

RDD提供计算最佳位置,体现了数据本地化。体现了大数据中“计算移动数据不移动”的理念。

(6)RDD中最小的单元是partition;

 4、RDD的创建方式(共三种)

4.1、通过已经存在的scala集合去构建(少用)

 rdd3=sc.makeRDD(List(1,2,3,4))

4.2、加载外部的数据源去构建

 rdd1=sc.textFile("/words.txt")

4.3、从已经存在的rdd进行转换生成一个新的rdd

 rdd2=rdd1.flatMap(_.split(" "))
val rdd3=rdd2.map((_,1))

5、RDD的算子分类

5.1、transformation转换算子

根据已经存在的rdd转换生成一个新的rdd, 它是延迟加载它不会立即执行

      <1> map

              将一个RDD中的每个数据项,通过map中的函数映射变为一个新的元素;

              特点:输入一条,输出一条数据

     <2> flatMap

              扁平化输出:先map后flat;与map类似,每个输入项可以映射为0到多个输出项

    <3> filter

             过滤符合条件的记录数,true保留,false过滤掉

   <4> reduceByKey

          将相同的Key根据相应的逻辑进行处理;

          与groupByKey的区别:当调用 groupByKey时,所有的键值对(key-value pair) 都会被移动,在网络上传输这些数据非常没必要,因此避免使用 GroupByKey;

    <5> sample

            随机抽样算子,根据传进去的小数按比例进行又放回或者无放回的抽样;

    <6> sortByKey/sortBy

            作用在K,V格式的RDD上,对key进行升序或者降序排序

    <7> join,leftOuterJoin,rightOuterJoin,fullOuterJoin

作用在K,V格式的RDD上。根据K进行连接,对(K,V)join(K,W)返回(K,(V,W));

            join后的分区数与父RDD分区数多的那一个相同。

    <8> union

合并两个数据集。两个数据集的类型要一致。返回新的RDD的分区数是合并RDD分区数的总和。

    <9> intersection

取两个数据集的交集,返回新的RDD与父RDD分区多的一致

    <10> subtract

取两个数据集的差集,结果RDD的分区数与subtract前面的RDD的分区数一致。

    <11> mapPartitions

与map类似,遍历的单位是每个partition上的数据。

    <12> distinct

               对源RDD进行去重后返回一个新的RDD

    <13> cogroup

当调用类型(K,V)和(K,W)的数据上时,返回一个数据集(K,(Iterable<V>,Iterable<W>)),子RDD的分区与父RDD多的一致。

5.2、action 行动算子

它会真正触发任务的运行;将rdd的计算的结果数据返回给Driver端,或者是保存结果数据到外部存储介质中。

在Spark中有一个action算子,就有一个job。

    <1> count

            返回数据集中的元素数。会在结果计算完成后回收到Driver端;

    <2> take(num)

             返回一个包含数据集前n个元素的集合;

    <3> first

            first=take(1),返回数据集中的第一个元素;

    <4> foreach

             循环遍历数据集中的每个元素,运行相应的逻辑;

    <5> collect

            将计算结果回收到Driver端

    <6> mapPartitions

            一次获取的是一个分区的数据(hdfs);

           正常情况下,mapPartitions 是一个高性能的算子;因为每次处理的是一个分区的数据,减少了去获取数据的次数;但是如果我们的分区如果设置得不合理,有可能导致每个分区里面的数据量过大。

    <8> mapPartitionsWithIndex

            每次获取和处理的就是一个分区的数据,并且知道处理的分区的分区号是啥

    <9> union

           当要将两个RDD合并时,便要用到union和join,其中union只是简单的将两个RDD累加起来,可以看做List的addAll方法。就想List中一样,当使用union及join时,必须保证两个RDD的泛型是一致的;

           返回新的RDD的分区数是合并RDD分区数的总和

    <10> groupByKey

              groupBy是将RDD中的元素进行分组,组名是call方法中的返回值,而顾名思义groupByKey是将PairRDD中拥有相同key值得元素归为一组

    <11> join

            join是将两个PairRDD合并,并将有相同key的元素分为一组,可以理解为groupByKey和Union的结合;

            join后的分区数与父RDD分区数多的那一个相同

    <12> cogroup

             对两个RDD中的KV元素,每个RDD中相同key中的元素分别聚合成一个集合。与reduceByKey不同的是针对两个RDD中相同的key的元素进行合并

    <13> foreachPartition

            遍历的数据是每个partition的数据。

5.3、持久化算子(也叫缓存机制,详情参看8/9/10章节)

持久化算子有三种,cache,persist,checkpoint,以上算子都可以将RDD持久化,持久化的单位是partitioncache和persist都是懒执行的。必须有一个action类算子触发执行。checkpoint算子不仅能将RDD持久化到磁盘和hdfs,还能切断RDD之间的依赖关系。

    <1> cache()

默认将数据存储在内存中,懒执行;底层调用的是persist方法;

cache()=persist()=persisit(StroageLevel.MEMORY_ONLY)

    <2> persist()

            1)可以手动指定持久化的级别(共12种);

            2)常用级别:

                 MEMORY_ONLY

                 MEMORY_ONLY_SER:序列化占空间小,但使用时需要反序列化

                 MEMORY_AND_DISK:先往内存放,内存不够再往磁盘放

                 MEMORY_AND_DISK_SER

           3)尽量避免使用"_2"和DISK_ONLY级别:因为浪费空间

    <3>checkpoint()

           当RDD计算逻辑复杂,lineage非常长时,可以对RDD进行checkpoint,默认将数据持久化到指定目录中(HDFS中分布式的持久化)。

           checkpoint将RDD持久化到磁盘,还可以切断RDD之间的依赖关系。checkpoint目录数据当application执行完之后不会被清除。

6、RDD的依赖关系

RDD和它依赖的父RDD的关系有两种不同的类型:窄依赖(narrow dependency)和宽依赖(wide dependency),如图。

6.1、窄依赖

父RDD和子RDD partition之间的关系是一对一的。或者父RDD一个partition只对应一个子RDD的partition情况下的父RDD和子RDD partition关系是多对一的。所有的窄依赖不会产生shuffle。

窄依赖相关算子操作:
map/flatMap/filter/union等等。

6.2、宽依赖

父RDD与子RDD partition之间的关系是一对多。会有shuffle的产生。

宽依赖相关算子操作:
reduceByKey/sortByKey/groupBy/groupByKey/join等等。

7、lineage(血统)

(1)RDD只支持粗粒度转换,即只记录单个块上执行的单个操作

(2)将创建RDD的一系列Lineage(即血统)记录下来,以便恢复丢失的分区

(3)RDD的Lineage会记录RDD的元数据信息和转换行为,lineage保存了RDD的依赖关系,当该RDD的部分分区数据丢失时,它可以根据这些信息来重新运算和恢复丢失的数据分区。

(比如rdd3依赖rdd2,rdd2依赖rdd1;若rdd3和rdd2中2号分区数据丢失,则可以通过rdd1调用flatMap、map等操作,重新计算得到对应的数据。)

8、RDD的缓存机制

可以把一个rdd的数据缓存起来后续有其他的job需要用到该rdd的结果数据,可以直接从缓存中获取得到,避免了重复计算。缓存是加快后续对该数据的访问操作。

8.1、如何对rdd设置缓存

RDD通过persist方法或cache方法可以将前面的计算结果缓存。但是两者都是属于懒加载方式,需调用action算子触发。

val rdd3=rdd2.cache
rdd3.collect   //action算子触发

val rdd4=rdd3.map((_,1))
val rdd5=rdd4.persist(缓存级别)    //此处缓存级别需要填写具体路径
rdd5.collect

8.2、什么时候设置缓存

A)某个rdd的数据后期被使用了多次

  • 如上图所示的计算逻辑:
  • 当第一次使用rdd2做相应的算子操作得到rdd3的时候,就会从rdd1开始计算,先读取HDFS上的文件,然后对rdd1 做对应的算子操作得到rdd2,再由rdd2计算之后得到rdd3。同样为了计算得到rdd4,前面的逻辑会被重新计算。
  • 默认情况下多次对一个rdd执行算子操作, rdd都会对这个rdd及之前的父rdd全部重新计算一次。 这种情况在实际开发代码的时候会经常遇到,但是我们一定要避免一个rdd重复计算多次,否则会导致性能急剧降低。
  • 可以把多次使用到的rdd,也就是公共rdd进行持久化,避免后续需要,再次重新计算,提升效率。 

B)为了获取得到一个rdd的结果数据,经过了大量的算子操作或者是计算逻辑比较复杂,即某个rdd的数据来之不易。如:

val rdd2=rdd1.flatMap(函数).map(函数).reduceByKey(函数).xxx.xxx.xxx.xxx.xxx(大量的算子操作,比如几十上百个算子操作),此时就将rdd2缓存起来。

8.3、清除缓存数据

1、自动清除

  • 当application应用程序结束之后,对应的缓存数据也就自动清除;

2、手动清除

  • 调用rdd的unpersist方法。

9、RDD的checkpoint机制

它是提供了一种相对而言更加可靠的数据持久化方式。它是把数据保存在分布式文件系统,比如HDFS上。这里就是利用了HDFS高可用性,高容错性(多副本)来最大程度保证数据的安全性。

9.1、如何设置checkpoint

(1)在hdfs上设置一个checkpoint目录

setCheckpointDir("hdfs://node01:8020/checkpoint")

hdfs上生成checkpoint目录,如图:

 

(2)对需要做checkpoint操作的rdd调用checkpoint方法

 rdd1=sc.textFile("/words.txt")
 rdd1.checkpoint
 val rdd2=rdd1.flatMap(_.split(" "))

(3)最后需要有一个action操作去触发任务的运行

rdd2.collect

9.2、checkpoint 的执行原理:

  1. 当RDD的job执行完毕后,会从finalRDD从后往前回溯
  2. 当回溯到某一个RDD调用了checkpoint方法,会对当前的RDD做一个标记。
  3. Spark框架会自动启动一个新的job,重新计算这个RDD的数据,将数据持久化到HDFS上。
  4. 优化:对RDD执行checkpoint之前,最好对这个RDD先执行cache,这样新启动的job只需要将内存中的数据拷贝到HDFS上就可以,省去了重新计算这一步。

10、cache ,persist ,checkpoint 区别

10.1、cache 与persist:

1)cache()和persist()都是懒执行需要action算子触发最小持久化的单位是partition

2)cache和persist算子的返回值可以赋值给一个变量,在其他job中直接使用这个变量就是持久化的数据了;

3)若使用第二种方式,后面不能紧跟action算子

4)cache和persist算子持久化的数据当application执行完成之后会被自动清除

5)cache默认将RDD数据持久化到内存中,本质是调用persist;而persist可以指定持久化的级别(12种),把数据持久化到内存、磁盘或堆外,最常用的是MEMORY_ONLY和MEMORY_AND_DISK;

 

6)两者都不会开启其他新的任务,一个action操作就对应一个job;

7)两者都不会改变rdd的依赖关系,程序运行完成后对应的缓存数据就自动消失。

10.2、checkpoint:

1)checkpoint将RDD持久化到磁盘或hdfs上,需要action算子操作触发

2)会开启新的job执行checkpoint操作;

3)checkpoint目录数据当application执行完成之后不会被清除

4)checkpoint执行过程中会切断RDD之间的依赖关系,可以基于checkpoint的目录进行数据的恢复

5)checkpoint不常用于持久化RDD,常用于保存元数据,而不是持久RDD。

10.3、persist(StorageLevel.DISK_ONLY) 与 checkpoint:

1)前者虽然可以将 RDD 的 partition 持久化到磁盘,但该 partition 由 blockManager 管理一旦 driver program 执行结束,也就是 executor 所在进程 CoarseGrainedExecutorBackend stopblockManager 也会 stop被 cache 到磁盘上的 RDD 也会被清空(整个 blockManager 使用的 local 文件夹被删除)。

2)而 checkpoint 将 RDD 持久化到 HDFS 或本地文件夹如果不被手动 remove 掉( 话说怎么 remove checkpoint 过的 RDD? ),是一直存在的,也就是说可以被下一个 driver program 使用,而 cached RDD 不能被其他 dirver program 使用。

11、DAG有向无环图

DAG(Directed Acyclic Graph) 叫做有向无环图(有方向,无闭环,代表着数据的流向),原始的RDD通过一系列的转换就形成了DAG。

11.1、DAG划分stage

(1)一个Job会被拆分为多组Task,每组任务被称为一个stage

(2)stage表示不同的调度阶段,一个spark job会对应产生很多个stage

(3)stage类型一共有2种:

  • ShuffleMapStage:最后一个shuffle之前的所有变换的Stage叫ShuffleMapStage,它对应的task是shuffleMapTask
  • ResultStage:最后一个shuffle之后操作的Stage叫ResultStage,它是最后一个Stage。它对应的task是ResultTask

11.2、为什么要划分stage

根据RDD之间依赖关系的不同将DAG划分成不同的Stage(调度阶段):

  • 对于窄依赖,partition的转换处理在一个Stage中完成计算;
  • 对于宽依赖,由于有Shuffle的存在,只能在parent RDD处理完成后,才能开始接下来的计算。
  • 由于划分完stage之后,在同一个stage中只有窄依赖,没有宽依赖,可以实现流水线计算,stage中的每一个分区对应一个task,在同一个stage中就有很多可以并行运行的task。

11.3、如何划分stage

划分stage的依据就是宽依赖

(1) 首先根据rdd的算子操作顺序生成DAG有向无环图,接下来从最后一个rdd往前推,创建一个新的stage,把该rdd加入到该stage中,它是最后一个stage。
(2) 在往前推的过程中运行遇到了窄依赖就把该rdd加入到本stage中,如果遇到了宽依赖,就从宽依赖切开,那么最后一个stage也就结束了。
(3) 重新创建一个新的stage,按照第二个步骤继续往前推,一直到最开始的rdd,整个划分stage也就结束了。

11.4、stage与stage之间的关系

划分完stage之后,每一个stage中有很多可以并行运行的task,后期把每一个stage中的task封装在一个taskSet集合中,最后把一个一个的taskSet集合提交到worker节点上的executor进程中运行。
rdd与rdd之间存在依赖关系,stage与stage之前也存在依赖关系,前面stage中的task先运行,运行完成了再运行后面stage中的task,也就是说后面stage中的task输入数据是前面stage中task的输出结果数据

11.5、stage计算模式

pipeline管道计算模式,pipeline只是一种计算思想,模式。

每个task相当于执行了一个高阶函数,f4(f3(f2(f1(“.....”)))),这种模式就称为pipeline管道计算模式。

(1)数据一直在管道里面什么时候数据会落地?

  1. 对RDD进行持久化。
  2. shuffle write的时候。

(2)Stage的task并行度是由stage的最后一个RDD的分区数来决定的 。

12、collect 算子

  • 它是一个action操作,会触发任务的运行
  • 它会把RDD的数据进行收集之后,以数组的形式返回给Driver端;

 

(1)默认Driver端的内存大小为1G,由参数 spark.driver.memory 设置;

(2)如果某个rdd的数据量超过了Driver端默认的1G内存,对rdd调用collect操作,这里会出现Driver端的内存溢出,所有这个collect操作存在一定的风险,实际开发代码一般不会使用。

(3)实际企业中一般都会把该参数调大,比如5G/10G等

可以在代码中修改该参数,如下:

 

 SparkConf().set("spark.driver.memory","5G")

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值