Spark总结

Saprk Core

Spark构成

spark包括Spark Core最基础,最核心的功能,Spark SQL操作结构化数据的组件,Spark Streaming针对实时数据进行流式计算的组件,Spark MLlib机器学习算法库,Spark GraphX面向图计算提供的框架与算法

Spark运行环境

1、Local模式

不需要其他任何节点资源就可以在本地执行Spark代码的环境,一般用于教学,调试,演示等。

2、Standalone模式

只使用Spark自身节点运行的集群模式,也就是独立部署模式。独立部署模式由Spark自身提供计算资源,无需其他框架提供资源。这种方式降低了和其他第三方资源框架的耦合性,独立性非常强。此模式体现了经典的master-slave模式。

3、Yarn模式

虽然Standalone模式无需其他框架提供资源,独立性非常强,但是Spark主要时计算框架,而不是资源调度框架,所以本身提供的资源调度并不是他的强项,所以还是和其他专业的资源调度框架集成要更加靠谱一些,即Yarn模式。

4、K8s & Mesos模式

Apache下的开源分布式资源管理系统,国内使用的不多。

5、Windows模式

便于我们学习。

Spark运行架构

核心组件

Driver:
Excutor:
Master & Slaver:
ApplicationMaster:

核心概念

Excutor与Core:
并行度:
有向无环图:

提交流程

Yarn Client和Yarn Cluster两种模式的主要区别在于:Driver程序的运行节点位置。
Yarn Client模式(Driver就是本地机器)
Client模式将用于监控和调度的 Driver模块在客户端执行,而不是在Yarn中,所以一般用于测试。
1、Driver在任务提交的本地机器上运行
2、Driver启动后会和ResourceManager通讯申请启动ApplicationMaster
3、ResourceManager分配 container,在合适的NodeManager上启动ApplicationMaster,负责向ResourceManager申请Executor内存
4、ResourceManager接到ApplicationMaster的资源申请后会分配container,然后ApplicationMaster在资源分配指定的NodeManager上启动Executor进程
5、Executor进程启动后会向Driver 反向注册,Executor 全部注册完成后Driver开始执行main函数
6、之后执行到Action算子时,触发一个Job,并根据宽依赖开始划分stage,每个stage 生成对应的TaskSet,之后将 task分发到各个Executor 上执行。
Yarn-Cluster模式(在合适的NodeManager上启动的ApplicationMaster就是Driver)
Cluster模式将用于监控和调度的Driver模块启动在Yarn集群资源中执行。一般应用于实际生产环境。
1、在YARN Cluster模式下,任务提交后会和 ResourceManager通讯申请启动ApplicationMaster,
2、随后ResourceManager分配container,在合适的NodeManager 上启动ApplicationMaster,此时的ApplicationMaster就是Driver。
3、Driver启动后向ResourceManager申请Executor内存,ResourceManager接到ApplicationMaster的资源申请后会分配container,然后在合适的NodeManager上启动Executor进程。
4、Executor进程启动后会向Driver 反向注册,Executor 全部注册完成后Driver开始执行main函数。
5、之后执行到Action算子时,触发一个Job,并根据宽依赖开始划分stage,每个stage生成对应的TaskSet,之后将task分发到各个Executor上执行。

Spark核心编程

RDD

核心属性

分区列表
分区计算函数
RDD之间的依赖关系
分区器
首选位置

执行原理

(1)启动Yarn集群
(2)Spark通过申请资源节点创建调度节点(Driver)和计算节点(Excutor)
(3)Spark框架根据需求将计算逻辑根据分区划分成不同的任务
(4)调度节点将任务根据计算节点状态发送到对应的计算节点进行计算

基础编程

RDD的创建

(1)从集合(内存)中创建RDD
Spark主要提供了两个方法,parallelize和makeRDD,但是从底层代码来讲,makeRDD方法就是parallelize方法(makeRDD方法中调用了parallelize方法)。
(2)从外部存储(文件)创建RDD
由外部存储系统的数据集创建RDD,包括本地的文件系统,所有Hadoop支持的数据集,比如HDFS、HBase等。
(3)从其他RDD创建
主要时通过一个RDD运算完之后,产生新的RDD。
(4)直接创建RDD(new)
使用new直接构造RDD,一般时Spark框架自身使用。

RDD并行度与分区

默认情况就下,Spark可以将一个作业切分多个任务后发送给Excutor节点并行计算,而能够并行计算的任务数量我们称之为并行度。这个数量 可以在构建RDD时指定。如下:
(1)makeRDD()可以传入第二个参数,作用为设置分区数量
如果省略的话会使用默认值,默认值为 scheduler.conf.getInt(“spark.default.parallelism”,totalCores)
spark在默认情况下,从配置对象中获取配置参数,spark.default.parallelism
如果获取不到,那么使用totalCores属性,这个属性取值为当前运行环境的最大可用核数
(2)textFile()也可以设定分区,默认为minPartition = math.min(defaultPartition,2)
如果不想使用默认分区数量,可以通过第二个参数指定分区数
分区数量的计算方式: goalSize = totalSize(文件的总字节数) / numsplits(设定的分区数)
如果设定的分区数为2,totalSize为7,会余出一个字节,这时设定的numsplits不起作用,分区还是会有三个
如果数据源为多个文件,那么计算分区时以文件为单位进行分区

RDD转换算子
RDD行动算子
RDD序列化

从计算的角度来看,算子以外的代码都是在Driver端执行的,算子里面的代码都是在Excutor端执行,在Scala的函数式编程中,就会导致算子内的代码总是会用到算子外的数据,如果使用的算子外的数据无法序列化,就意味着无法传值给Excutor端执行,就会发生错误。

RDD依赖关系
RDD血缘关系

RDD只支持粗粒度转换,即在大量记录上执行的单个操作。将创建RDD的一系列Lineage(血统)记录下来,以便恢复丢失的分区。RDD 的Lineage 会记录RDD的元数据信息和转换行为,当该RDD的部分分区数据丢失时,它可以根据这些信息来重新运算和恢复丢失的数据分区。

RDD依赖关系

这里所谓的依赖关系,其实就是相邻两个RDD之间的关系。

RDD窄依赖

窄依赖表示每一个父(上游)RDD的Partition最多被子(下游)RDD的一个Partition使用,窄依赖我们形象的比喻为独生子女。

RDD宽依赖

宽依赖表示同一个父(上游)RDD的Partition被多个子(下游)RDD的Partition依赖,会引起Shuffle,总结:宽依赖我们形象的比喻为多生。

RDD阶段划分

stage = shuffle数量 + 1

RDD任务划分

RDD任务切分中间分为: Application、Job、Stage和 Task
Application:初始化一个SparkContext即生成一个Application;
Job:一个Action算子就会生成一个Job;
Stage:Stage 等于宽依赖(ShuffleDependency)的个数加1;
Task:一个Stage阶段中,最后一个RDD的分区个数就是Task的个数。
注意:Application -> Job -> Stage -> Task每一层都是1对n的关系。

RDD持久化
RDD Cache缓存

RDD通过Cache或者Persist方法将前面的计算结果缓存,默认情况下会把数据以缓存在JVM的堆内存中。但是并不是这两个方法被调用时立即缓存,而是触发后面的action算子时,该RDD将会被缓存在计算节点的内存中,并供后面重用。
缓存有可能丢失,或者存储于内存的数据由于内存不足而被删除,RDD的缓存容错机制保证了即使缓存丢失也能保证计算的正确执行。通过基于RDD的一系列转换,丢失的数据会被重算,由于RDD的各个Partition是相对独立的,因此只需要计算丢失的部分即可,并不需要重算全部Partition。
Spark会自动对一些Shuffle操作的中间数据做持久化操作(比如: reduceByKey)。这样做的目的是为了当一个节点 Shuffle 失败了避免重新计算整个输入。但是,在实际使用的时候,如果想重用数据,仍然建议调用persist 或cache。

RDD CheckPoint检查点

所谓的检查点其实就是通过将RDD中间结果写入磁盘
由于血缘依赖过长会造成容错成本过高,这样就不如在中间阶段做检查点容错,如果检查点之后有节点出现问题,可以从检查点开始重做血缘,减少了开销。
对RDD进行checkpoint操作并不会马上被执行,必须执行Action操作才能触发。

缓存和检查点的区别

1)Cache缓存只是将数据保存起来,不切断血缘依赖。Checkpoint检查点切断血缘依赖。
2)Cache缓存的数据通常存储在磁盘、内存等地方,可靠性低。Checkpoint的数据通常存储在HDFS等容错、高可用的文件系统,可靠性高。
3)建议对checkpoint()的RDD使用Cache缓存,这样checkpoint的job只需从Cache缓存中读取数据即可,否则需要再从头计算一次RDD。

RDD分区器

Spark目前支持Hash分区和Range分区,和用户自定义分区。Hash分区为当前的默认分区。分区器直接决定了RDD中分区的个数、RDD中每条数据经过Shuffle后进入哪个分区,进而决定了Reduce的个数。
(1)只有Key-Value类型的RDD才有分区器,非 Key-Value类型的RDD分区的值是None
(2)每个RDD的分区ID范围: 0 ~ (numPartitions - 1),决定这个值是属于哪个分区的。
1)Hash分区:对于给定的key,计算其hashCode,并除以分区个数取余。
2)Range分区:将一定范围内的数据映射到一个分区中,尽量保证每个分区数据均匀,而且分区间有序。

RDD文件读取与保存

Spark的数据读取及数据保存可以从两个维度来作区分:文件格式以及文件系统。
文件格式分为: text文件、csv文件、sequence文件以及 Object 文件;
文件系统分为:本地文件系统、HDFS、HBASE以及数据库。
(1)text文件

//读取输入文件
val inputRDD: RDD [ string] = sc.textFile ("input/1.txt")
//保存数据
inputRDD.saveAsTextFile ("output")

(2)sequence文件
SequenceFile 文件是Hadoop用来存储二进制形式的key-value对而设计的一种平面文件(FlatFile)。在 SparkContext 中,可以调用sequenceFile[keyClass,valueClass](path)。

//保存数据为sequenceFile
dataRDD.saveAssequenceFile("output")

//读取sequenceFile文件
sc.sequenceFile[Int,Int]("output").collect().foreach(println)

(3)object对象文件
对象文件是将对象序列化后保存的文件,采用Java 的序列化机制。可以通过objectFile[T:ClassTag](path)函数接收一个路径,读取对象文件,返回对应的RDD,也可以通过调用saveAsObjectFile()实现对对象文件的输出。因为是序列化所以要指定类型。

//保存数据
dataRDD.saveAsObjectFile("Output")

//读取数据
sc.objectFile[Int]("Output").collect().foreach(println)

累加器

分布式共享只写变量
累加器用来把Executor端变量信息聚合到 Driver端。在 Driver程序中定义的变量,在Executor端的每个Task都会得到这个变量的一份新的副本,每个task更新这些副本的值后,传回Driver端进行merge。

广播变量

分布式共享只读变量
广播变量用来高效分发较大的对象。向所有工作节点发送一个较大的只读值,以供一个或多个Spark操作使用。比如,如果你的应用需要向所有节点发送一个较大的只读查询表,广播变量用起来都很顺手。在多个并行操作中使用同一个变量,但是Spark 会为每个任务分别发送。

Spark SQL

DataFrame

在Spark 中,DataFrame是一种以RDD为基础的分布式数据集,类似于传统数据库中的二维表格。DataFrame 与RDD的主要区别在于,前者带有schema元信息,即 DataFrame所表示的二维表数据集的每一列都带有名称和类型。这使得Spark SQL得以洞察更多的结构信息,从而对藏于DataFrame背后的数据源以及作用于DataFrame之上的变换进行了针对性的优化,最终达到大幅提升运行时效率的目标。反观RDD,由于无从得知所存数据元素的具体内部结构,Spark Core 只能在 stage层面进行简单、通用的流水线优化。
直观体现了RDD与DataFrame的区别

DataSet

DataSet是分布式数据集合。DataSet是Spark 1.6中添加的一个新抽象,是DataFrame的一个扩展。它提供了RDD的优势(强类型,使用强大的lambda 函数的能力)以及SparksQL优化执行引擎的优点。DataSet也可以使用功能性的转换(操作 map,flatMap,filter等等)。
(1)DataSet是 DataFrame API的一个扩展,是 SparkSQL最新的数据抽象
(2)用户友好的API风格,既具有类型安全检查也具有DataFrame的查询优化特性;
(3)用样例类来对DataSet中定义数据的结构信息,样例类中每个属性的名称直接映射到DataSet中的字段名称;
(4)DataSet是强类型的。比如可以有DataSet[Car],DataSet[Person]。
(5)DataFrame是DataSet的特列,DataFrame=DataSet[Row],所以可以通过as方法将DataFrame转换为DataSet。Row是一个类型,跟Car、Person这些的类型一样,所有的表结构信息都用Row来表示。获取数据时需要指定顺序。

RDD、DataFrame、DataSet三者的关系

在SparkSQL 中 Spark为我们提供了两个新的抽象,分别是DataFrame和 DataSet。他们和RDD有什么区别呢?首先从版本的产生上来看:
Spark1.0 =>RDD
Spark1.3 => DataFrame
Spark1.6=> Dataset
如果同样的数据都给到这三个数据结构,他们分别计算之后,都会给出相同的结果。不同是的他们的执行效率和执行方式。在后期的Spark版本中,DataSet有可能会逐步取代RDD和 DataFrame成为唯一的API接口。

三者的共性

(1)RDD、DataFrame、DataSet全都是spark平台下的分布式弹性数据集,为处理超大型数据提供便利;
(2)三者都有惰性机制,在进行创建、转换,如 map方法时,不会立即执行,只有在遇到Action 如 foreach时,三者才会开始遍历运算;
(3)三者有许多共同的函数,如filter,排序等;
(4)在对 DataFrame和 Dataset进行许多操作时都需要这个包:import spark.implicits._(在创建好SparkSession对象后尽量直接导入)
(5)三者都会根据Spark的内存情况自动缓存运算,这样即使数据量很大,也不用担心会内存溢出;
(6)三者都有partition 的概念;
(7)DataFrame和 DataSet均可使用模式匹配获取各个字段的值和类型;

三者的区别

1)RDD
(1)RDD一般和spark mllib 同时使用
(2)RDD不支持sparksqI操作

2)DataFrame
(1)与RDD和 Dataset不同,DataFrame每一行的类型固定为Row,每一列的值没法直接访问,只有通过解析才能获取各个字段的值
(2)DataFrame 与 DataSet一般不与spark mllib 同时使用
(3)DataFrame 与 DataSet均支持SparkSQL的操作,比如select,groupby之类,还能注册临时表/视窗,进行 sql语句操作
(4)DataFrame 与 DataSet支持一些特别方便的保存方式,比如保存成csv,可以带上表头,这样每一列的字段名一目了然

3)DataSet
(1)Dataset和 DataFrame拥有完全相同的成员函数,区别只是每一行的数据类型不同。DataFrame其实就是DataSet的一个特例

type DataFrame = Dataset[Row]

(2)DataFrame也可以叫 Dataset[Row],每一行的类型是Row,不解析,每一行究竟有哪些字段,各个字段又是什么类型都无从得知,只能用上面提到的getAS方法或者共性中的第七条提到的模式匹配拿出特定字段。而 Dataset中,每一行是什么类型是不一定的,在自定义了case class 之后可以很自由的获得每一行的信息

RDD、DataFrame、DataSet三者的相互转换

//RDD和DataFrame的相互转换
//RDD -> DataFrame
val df = rdd.toDF("id")	
//DataFrame -> RDD
val RDD = df.rdd			


//RDD与DataSet的相互转换
//RDD -> DataSet
case class User(name:String, age:Int)
val rdd = sc.makeRDD(List(User("zhangsan",30), User("lisi",49)))
val ds = rdd.toDS
//DataSet -> RDD
val rdd1 = ds.rdd


//DataFrame与DataSet的相互转换
//DataFrame -> DataSet
case class User(name:String,age:Int)	
val df = sc.makeRDD(List(("zhangsan",30),("lisi",49))).toDF("name","age")
val ds = df.as[User]
//DataSet -> DataFrame
ds.toDF

SparkStreaming

Dstream转换

无状态转换操作

无状态转化操作就是把简单的RDD转化操作应用到每个批次上,也就是转化DStream中的每一个RDD。注意,针对键值对的DStream转化操作(比如reduceByKey())要添加import StreamingContext._才能在 Scala中使用。
比如map(),flatMap(),filter(),reduceByKey(),groupByKey()都是无状态转换操作

需要记住的是,尽管这些函数看起来像作用在整个流上一样,但事实上每个DStream在内部是由许多RDD(批次)组成,且无状态转化操作是分别应用到每个RDD上的。
例如: reduceByKey()会归约每个时间区间中的数据,但不会归约不同区间之间的数据。

transform

//Code Driver端
val wordAndCountDstream : Dstream[(String,Int)] = lineDstream.transform(rdd =>{
    //Code Driver端(周期性执行)
    val words:RDD[String] = rdd.flatMap(_.split(" "))	//Code Excutor端
    val wordAndOne:RDD[(String,Int)] = words.map((_,1))
    val value:RDD[(String,Int)] = wordAndOne.reduceByKey(_+_)
    value
})

join
两个流之间的join需要两个流的批次大小一致,这样才能做到同时触发计算。计算过程就是对当前批次的两个流中各自的RDD进行 join,与两个RDD的join效果相同。

有状态转换操作

在这里插入图片描述

UpdateStateByKey

WindowOperations

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值