文章目录
前言
RDD是 Spark 的基石,对于初学者来说,它是非常重要的一部分内容,是实现 Spark 数据处理的核心抽象。RDD 是一个抽象类,它代表一个不可变、可分区、里面的元素可并行计算的集合。将RDD这个核心概念理解好了,后续学习起Spark也显得游刃有余。
一、RDD编程
RDD是Spark中的核心概念,它是一个容错,可分布并行执行的分布式数据集。
1.1RDD五大特征
1.RDD是由一系列partition组成(block块对应partition),textFile底层调用的是MR读取hdfs上的数据的方法,默认一个block块对应一个split,split的大小和block大小一致,可以自己调整。
2.函数作用在每一个partition(split)上
3.RDD之间有一系列的依赖关系(容错机制)
4. 对key-value RDDs来说,存在一个分区器(Partitioner)
5.RDD 提供一系列最佳的计算位置
1.2RDD的特点
1.分区:RDD逻辑上是分区的,每个分区的数据是抽象存在的,
2.只读:RDD是只读的,要想改变RDD中的数据,只能在现有的RDD基础上创建新的RDD
3.依赖:RDD通过操作算子进行转换,转换得到的新RDD包含了从其他RDD衍生所必需的信息,RDDs之间维护着这种血缘关系(lineage),依赖包含两种,窄依赖(1:1or 1:n),宽依赖(n:m).
4.缓存:如果在应用程序中多次使用同一个RDD,可以将该RDD缓存起来,该RDD只有在第一次计算的时候会根据血缘关系得到分区的数据,在后续其他地方用到该RDD的时候,会直接从缓存处取而不用再根据血缘关系计算.
5.checkpoint:RDD支持 checkpoint 将数据保存到持久化的存储中,这样就可以切断之前的血缘关系,不必通过复杂的血缘关系而进行计算,大大的提高了性能。
1.3RDD的操作算子
Transformation:用来对RDD进行转化,这个操作时延迟执行的(或者说是 Lazy 的);返回一个新的RDD
Action:用来触发RDD的计算;得到相关计算结果或者将结果保存的外部系统中;返回结果,集合(不会返回新的RDD)
二、RDD高阶编程特性
2.1序列化
在实际开发中会自定义一些对RDD的操作,此时需要注意的是:初始化工作是在Driver端进行的,实际运行程序是在Executor端进行的。这就涉及到了进程通信,是需要序列化的。
对于普通的类来说,不具备序列化的能力,一般会有以下两种解决方案:
方案一:使用case class 定义类
方案二:实现Serializable接口
2.2RDD依赖关系
RDD只支持粗粒度转换,即在大量记录上执行单个操作。RDD和它依赖的父RDD(s)的关系有两种不同的类型,即窄依赖(narrow dependency)和宽依赖(widedependency)。依赖有2个作用:其一用来解决数据容错;其二用来划分stage。
2.3RDD任务切分中角色概念
1.Driver program:初始化一个SparkContext即生成一个Spark应用
2.Job:一个Action算子就会生成一个Job
3.Stage:根据RDD之间的依赖关系的不同将Job划分成不同的Stage,遇到一个宽依赖则划分一个Stage
4.Task:Stage是一个TaskSet,将Stage划分的结果发送到不同的Executor执行即为一个Task
5.Task是Spark中任务调度的最小单位;每个Stage包含许多Task,这些Task执行的计算逻辑相同的,计算的数据是不同的
Driver programe->Job->Stage-> Task每一层都是1对n的关系
2.4.RDD持久化/缓存
RDD持久化或缓存,是Spark最重要的特征之一。可以说,缓存是Spark构建迭代式算法和快速交互式查询的关键因素。一般情况下,如果多个动作需要用到某个 RDD,而它的计算代价又很高,那么就应该把这个 RDD 缓存起来。缓存有可能丢失,或者存储于内存的数据由于内存不足而被删除。RDD的缓存的容错机制保证了即使缓存丢失也能保证计算的正确执行。
通过**persist()或cache()**方法可以标记一个要被持久化的RDD,持久化被触发,RDD将会被保留在计算节点的内存中并重用
persist()的参数可以指定持久化级别参数;使用cache()方法时,会调用persist(MEMORY_ONLY),即:cache()==persist(StorageLevel.Memeory_ONLY)
2.5 RDD容错机制Checkpoint
检查点本质是通过将RDD写入高可靠的磁盘,主要目的是为了容错。检查点通过将数据写入到HDFS文件系统实现了RDD的检查点功能。
cache 和 checkpoint 的区别:缓存把 RDD 计算出来然后放在内存中,但是 RDD 的依赖链不能丢掉,当某个点某个 executor 宕了,上面 cache 的RDD就会丢掉,需要通过依赖链重放计算。不同的是,checkpoint 是把RDD 保存在 HDFS中,是多副本可靠存储,此时依赖链可以丢掉,所以斩断了依赖链。
适合使用检查点机制的场景:
- DAG中的Lineage过长,如果重算,则开销太大
- 在宽依赖上做 Checkpoint 获得的收益更大
2.6 RDD分区器
只有Key-Value类型的RDD才可能有分区器,Value类型的RDD分区器的值是None。
分区器的作用:在 PairRDD(key,value) 中,很多操作都是基于key的,系统会按照key对数据进行重组,最常见的就是基于 Hash 的分区,此外还有一种复杂的基于抽样 Range 分区方法。
HashPartitioner:最简单、最常用,也是默认提供的分区器。对于给定的key,计算其hashCode,并除以分区的个数取余,如果余数小于0,则用余数+分区的个数,最后返回的值就是这个key所属的分区ID。该分区方法可以保证key相同的数据出现在同一个分区中。
RangePartitioner:简单的说就是将一定范围内的数映射到某一个分区内。在实现中,分界的算法尤为重要,用到了水塘抽样算法。sortByKey会使用RangePartitioner。
自定义分区器:Spark允许用户通过自定义的Partitioner对象,灵活的来控制RDD的分区方式
2.7广播变量
有时候需要在多个任务之间共享变量,或者在任务(Task)和Driver Program之间共享变量。
Spark提供了两种类型的变量:
广播变量(broadcast variables)累加器(accumulators)主要是为了优化Spark程序。
广播变量:将变量在节点的 Executor 之间进行共享(由Driver广播出去);
广播变量用来高效分发较大的对象。向所有工作节点(Executor)发送一个较大的只读值,以供一个或多个操作使用。
使用广播变量的过程:对一个类型 T 的对象调用 SparkContext.broadcast 创建出一个 Broadcast[T] 对象。任何可序列化的类型都可以这么实现(在 Driver 端)通过 value 属性访问该对象的值(在 Executor 中)变量只会被发到各个 Executor 一次,作为只读值处理.
2.8累加器
累加器的作用:
可以实现一个变量在不同的 Executor 端能保持状态的累加;
累计器在 Driver 端定义,读取;
在 Executor 中完成累加;Action触发一次,执行一次,触发多次,执行多次;
Spark三种类型的累加器:
LongAccumulator用来累加整数型
DoubleAccumulator用来累加浮点型
CollectionAccumulator用来累加集合元素
三、 初识Spark原理
3.1Standalone模式作业提交
Standalone 模式下有四个重要组成部分,分别是:
Driver:用户编写的 Spark 应用程序就运行在 Driver 上,由Driver 进程执行
Master:主要负责资源的调度和分配,并进行集群的监控等职责
Worker:Worker 运行在集群中的一台服务器上。负责管理该节点上的资源,负责启动启动节点上的 Executor
Executor:一个 Worker 上可以运行多个 Executor,Executor通过启动多个线程(task)对 RDD 的分区进行并行计算
SparkContext 中的三大组件:
DAGScheduler:负责将DAG划分成若干个Stage
TaskScheduler:将DAGScheduler提交的 Stage(Taskset)进行优先级排序,再将 task 发送到 Executor
SchedulerBackend:定义了许多与Executor事件相关的处理,包括:新的executor注册进来的时候记录executor的信息,增加全局的资源量(核数);executor更新状态,若任务完成的话,回收core;停止executor、removeexecutor等事件
Standalone模式下作业提交步骤:
1、启动应用程序,完成SparkContext的初始化
2、Driver向Master注册,申请资源
3、Master检查集群资源状况。若集群资源满足,通知Worker启动Executor
4、Executor启动后向Driver注册(称为反向注册)
5、Driver完成DAG的解析,得到Tasks,然后向Executor发送Task
6、Executor 向Driver汇总任务的执行情况
7、应用程序执行完毕,回收资源
3.2Shuffle原理
Spark、Hadoop中的shuffle是为了将随机排列的数据转换成具有一定规则的数据。
shuffle涉及到了本地磁盘(非hdfs)的读写和网络的传输,大多数Spark作业的性能主要就是消耗在了shuffle环节。因此shuffle性能的高低直接影响到了整个程序的运行效率。
1、Hash Base Shuffle V1
每个Shuffle Map Task需要为每个下游的Task创建一个单独的文件Shuffle过程中会生成海量的小文件。同时打开过多文件、低效的随机IO
2、Hash Base Shuffle V2
Hash Base Shuffle V2 核心思想:允许不同的task复用同一批磁盘文件,有效将多个task的磁盘文件进行一定程度上的合并,从而大幅度减少磁盘文件的数量,进而提升shuffle write的性能。一定程度上解决了Hash V1中的问题,但不彻底。
3、Sort Base Shuffle
Sort Base Shuffle大大减少了shuffle过程中产生的文件数,提高Shuffle的效率
3.3RDD编程优化
1、RDD复用
避免创建重复的RDD,对于同一份数据,只应该创建一个RDD
2、RDD缓存/持久化
对多次使用的RDD进行持久化,通过持久化将公共RDD的数据缓存到内存/磁盘中,之后对于公共RDD的计算都会从内存/磁盘中直接获取RDD数据,减少了重复计算,节约了很大资源。
RDD的持久化是可以进行序列化的,当内存无法将RDD的数据完整的进行存放的时候,可以考虑使用序列化的方式减小数据体积,将数据完整存储在内存中
4、使用高性能算子
1、避免使用groupByKey,根据场景选择使用高性能的聚合算子 reduceByKey、aggregateByKey
2、coalesce、repartition,在可能的情况下优先选择没有shuffle的操作
3、foreachPartition 优化输出操作
4、map、mapPartitions,选择合理的选择算子mapPartitions性能更好,但数据量大时容易导致OOM
5、用 repartitionAndSortWithinPartitions 替代 repartition + sort 操作
6、合理使用 cache、persist、checkpoint,选择合理的数据存储级别
7、filter的使用
5、设置合理的并行度
Spark作业中的并行度指各个stage的task的数量设置合理的并行度,让并行度与资源相匹配。简单来说就是在资源允许的前提下,并行度要设置的尽可能大,达到可以充分利用集群资源。合理的设置并行度,可以提升整个Spark作业的性能和运行速度
6、广播大变量
默认情况下,task中的算子中如果使用了外部变量,每个task都会获取一份变量的复本,这会造多余的网络传输和内存消耗
使用广播变量,只会在每个Executor保存一个副本,Executor的所有task共用此广播变量,这样就节约了网络及内存资源。