定义
Spark是一种基于内存的快速、通用、可扩展的大数据分析计算引擎
与Hadoop的根本差异
Spark和Hadoop的根本差异是多个作业之间的数据通信问题 : Spark多个作业之间数据通信是基于内存,而Hadoop是基于磁盘
核心模块
1.Spark Core中提供了Spark最基础与最核心的功能
2.Spark SQL是Spark用来操作结构化数据的组件
3.Spark Streaming是Spark平台上针对实时数据进行流式计算的组件,提供了丰富的处理数据流的API。
4.MLlib是Spark提供的一个机器学习算法库
5.GraphX是Spark面向图计算提供的框架与算法库。
运行环境
常用模式
1.Standalone(独立部署)模式
Spark自身节点运行的集群模式,由Spark自身提供计算资源。Spark的Standalone模式体现了经典的master-slave模式。
2.Yarn模式(最常用)
Spark与Yarn资源调度框架集成,在Yarn环境下工作
3.Windows模式
在windows系统下启动本地集群的方式,省略开虚拟机的繁琐,一般用于教学,调试,演示
运行架构
运行架构
park框架的核心是一个计算引擎,整体来说,它采用了标准 master-slave 的结构。
Driver表示master,负责管理整个集群中的作业任务调度Cluster Manager,负责资源调度
Executor 则是 slave,负责实际执行任务
核心组件
1.Driver:驱动程序,用于驱动整个spark的运行
负责:
①将用户程序转化为作业(job)
②在Executor之间调度任务(task)
③跟踪Executor的执行情况
④通过UI展示查询运行情况
2.Executor:集群中工作节点(Worker)中的一个JVM进程,负责在 Spark 作业中运行具体任务(Task),任务彼此之间相互独立。
负责:
①负责运行组成Spark应用的任务,并将结果返回给驱动器进程
②它们通过自身的块管理器(Block Manager)为用户程序中要求缓存的 RDD 提供内存式存储。
3.Master & Worker:Standalone模式下还需要的另外两个组件
Master是一个进程,主要负责资源的调度和分配,并进行集群的监控等职责,类似于Yarn环境中的RM
Worker也是进程,一个Worker运行在集群中的一台服务器上,由Master分配资源对数据进行并行的处理和计算,类似于Yarn环境中NM
4.ApplicationMaster
Hadoop用户向YARN集群提交应用程序时,提交程序中应该包含ApplicationMaster,用于向资源调度器申请执行任务的资源容器Container,运行用户自己的程序任务job,监控整个任务的执行,跟踪整个任务的状态,处理任务失败等异常情况。
说的简单点就是,ResourceManager(资源)和Driver(计算)之间的解耦合靠的就是ApplicationMaster。
核心概念
DAG(有向无环图)
由点和线组成的拓扑图形,该图形具有方向,不会闭环。
定义:Spark程序直接映射成的数据流的高级抽象模型。简单理解就是将整个程序计算的执行过程用图形表示出来,这样更直观,更便于理解,可以用于表示程序的拓扑结构。
术语总结
术语 | 解释 |
---|---|
Driver | 驱动程序,由驱动程序,驱动整个Spark任务的提交。哪个程序中有SparkContext,这个程序称为Driver |
Client | 运行Driver程序,提交程序的客户端 |
Cluster | 集群。可以有多种模式。 local模式,YARN模式,Standalone模式。在提交Job时,需要通过–master指定 job提交到哪个集群。由集群为Job分配资源,运行Excutor! |
Excutor | JVM进程,负责运行Job中的多个Task,用来计算的 |
Task | 线程级别,每个Job在执行时,根据调用的算子,将一个Job划分为若干个阶段。每个阶段会划分为若干Task。 多个Task交给Executor执行! |
Job | 任务。一个行动算子,会提交一个Job! |
Application | 应用。一个SparkContext所在的程序,称为一个应用!在一个应用中可以提交多个Job! |
Stage | 以shuffle为界,当在一个job任务中涉及shuffle操作时,会进行stage划分,产生一个或多个stage。 |
提交流程
简易提交流程
SparkCoentxt 所在的程序是Driver类,也是一个Application(应用)
程序提交流程
①
// 创建Spark的配置对象,整个应用的全局的配置对象
val conf: SparkConf = new SparkConf().setMaster("local").setAppName("My app")
// 创建应用上下文,用来创建编程模型
val sparkContext = new SparkContext(conf)
②
SparkContext所在的程序为一个Application(应用)
③
RDD的一个行动算子触发一个job,一个Application可以提交多个job
④
一个Job有若干个stage,DAGScheduler通过RDD算子为每一个job构建DAG,再基于RDD算子之间的宽、窄依赖来切分所涉算子,最终得到一个Stage集合。遇到宽依赖就划分stage,宽依赖是有shuffle操作的算子
stage切割规则:从后往前切
shuffle:
原因:为了在不同的分区间重新分配数据
过程:将父RDD中一个分区的数据输入到多个子RDD的分区。只要产生shuffle操作的算子,都是分两步执行。mapTask组织数据(shuffle write),reduceTask(读取数据后执行算子的操作,shuffle read)
功能:跨Executor和跨机器传输数据
性能影响:shuffle会涉及到磁盘I/O,序列化,网络I/O,比较消耗性能
⑤
一个stage由多个并行的task组成,而task并行度是由stage的最后一个RDD的分区数来决定的(其实一个stage里的分区数是一样的)
⑥
如果将数据处理结果存储到指定路径,那么数据会按分区存储,分区数就是参与计算的最后一个RDD的分区数
总结:
一个应用可以提交多个job,一个job由一个行动算子触发,spark根据job之间的RDD的依赖关系生成DAG,因为遇到有shuffle操作的算子才会出现数据的重新分配,所以一个stage中的分区数是一样的,spark计算会照着DAG流程来走(按照RDD的依赖关系走)。
理解RDD与DAG生成的先后顺序:
Spark 会记录 RDD 的生成和彼此间依赖关系。但是只有当行动算子进行操作时,Spark 才会根据 RDD 的依赖关系生成 DAG,并从起点开始真正的计算。
为了理解RDD,stage,task,并行度的关系,可以幻想成spark将之前记录的所有RDD按照其依赖关系生成DAG(将记录的所有RDD切分为多个stage),每个stage中按照依赖关系存放很多RDD,然后同一个stage的许多RDD有相同的分区即task并行度,每个RDD中又将数据分成很多个区,每个区对应一个task任务,然后如下图所示:
RDD将task交给executor执行,但RDD只封装计算逻辑,不存储数据,数据存放在而下executor中的cache中,最终所有的数据按DAG执行完整个流程
RDD
**定义:**弹性分布式数据集,是Spark中最基本的数据处理模型,只封装计算逻辑不存储数据,可以调用各种算子,不可变,是一个抽象类
RDD个人理解: 理解成一个不可变、可分区、里面的元素可并行计算的集合,但是RDD不存储数据只封装计算逻辑,RDD的算子理解成集合的函数
核心属性:
1.分区列表
RDD数据结构中存在分区列表,用于执行任务时并行计算,是实现分布式计算的重要属性。
2.分区计算函数
Spark在计算时,是使用分区函数对每一个分区进行计算
3.依赖关系
RDD是计算模型的封装,当需求中需要将多个计算模型进行组合时,就需要将多个RDD建立依赖关系
作用:
RDD在整个流程中主要用于将逻辑进行封装,并生成Task发送给Executor节点执行计算
RDD操作
创建:
1.直接创建
① 对接数据源,根据数据源中的数据,创建RDD。
Sparkcontext.textFile(”inptu“),返回的是HadoopRDD
②Sparkcontext.makeRDD(),返回的是 ParallelCollectionRDD,底层实现是 parallelize()
直接创建时,可以指定task并行度也就是数据的分区数,不指定的话是就是默认并行度—>defaultParallelism,
2.由一个旧的RDD转换为一个新的RDD
RDD转换算子
分类:
针对单值类型的转换算子: RDD[T]
针对K-V类型的转换算子: RDD[K,V]
针对双值类型的转换算子: RDD[T] , RDD[K]
根据RDD内存储的数据形式进行分类
注:
1.算子由RDD调用,转换计算之后返回一个新的RDD
2.所有的算子都在Executor端执行
单值转换算子:
算子 | 用法说明 |
---|---|
map | RDD中的每一个元素调用一次函数,不改变分区数,结果还在原分区 |
mapPartitions | RDD中的每一个分区调用一次函数,适用于数据的批处理 |
flapMap | 先map操作,然后扁平化 |
glom | 将同一分区数据转换成相同类型的数组存储 |
groupBy | 根据函数的返回值进行分组,返回值做K,同K的元素做V。最后返回一个map[ K -> List(V1,V2…)]集合 |
filter | 保留函数返回值为true的元素 |
sample | 根据指定的规则从数据集中抽取数据 |
distinct | 将数据集中重复的数据去重 |
coalesce | 用来减少分区数 |
repartition | 用来增加分区数 |
sortBy | 将元素按照函数的返回值进行排序,排序完成之后按照函数返回值排序的顺序一一对应的返回最初的元素 |
pipe | 允许每一个分区操作一个脚本,且该脚本可以操作分区内的元素 |
双值转换算子:
算子 | 说明 |
---|---|
intersection | 对原RDD和参数RDD求交集后返回一个新的RDD,两个数据类型要一样,分区数取决于上游的最大分区数 |
union | 取并集,分区数是上游所有的分区数之和,没有shuffle!将所有RDD的分区,依次合并放入到结果RDD中 |
subtract | 取差集,选择前一个RDD的分区数作为默认的结果的分区数 |
cartesian | 默认生成的分区数为两个RDD原先分区数的乘积 |
zip | 拉链,第一个RDD的元素做K,第二个RDD做V,RDD类型可以不一样但是分区数和各分区内元素个数要一样 |
zipWithIndex | 当前RDD的每一个元素和索引拉链 |
K-V转换算子:
算子 | 说明 |
---|---|
partitionBy | 使用指定的分区器进行重新分区,要求当前RDD的分区器和要重新分区传入的分区器不一致 |
reduceByKey | 将数据按照相同的Key分组,再对Value进行聚合,函数只能对Value进行操作,分区内先进行运算,分区间再进行运算 |
groupByKey | 将分区的数据直接转换为相同类型的内存数组进行后续处理 |
aggregateByKey(zeroValue)(f1,f2) | 将数据按照相同的Key分组,对key对应的value进行聚合,f1表示分区内zeroValue和一组V的计算(第一个V与zeroValue计算后作为一个新的zeroValue),f2表示分区间运算;zeroValue只在分区内参与运算 |
foldByKey | 当分区内计算规则和分区间计算规则相同时,aggregateByKey就可以简化为foldByKey |
combineByKey | 相同Key的第一个Value做zeroValue,之后不再参加分区内聚合 |
sortByKey | 对K-V类型的RDD,针对key对k-v进行排序 |
join | 在类型为(K,V)和(K,W)的RDD上调用,返回一个相同key对应的所有元素连接在一起的(K,(V,W))的RDD ;如果key不相等,对应的数据无法连接,如果key有重复的,那么数据会多次连接 |
cogroup | 在类型为(K,V)和(K,W)的RDD上调用,返回一个(K,(Iterable,Iterable))类型的RDD |
sortByKey: 针对key对k-v数据进行排序,有shuffle
sortBy: 针对单value排序,底层调用sortByKe
reduceByKey、foldByKey、aggregateByKey、combineByKey的区别:
从源码的角度来讲,四个算子的底层逻辑是相同的。
ReduceByKey不会对第一个value进行处理,分区内和分区间计算规则相同
AggregateByKey的算子会将初始值和第一个value使用分区内计算规则进行计算
FoldByKey的算子的分区内和分区间的计算规则相同,初始值和第一个value使用分区内计算规则
CombineByKey的第一个参数就是对第一个value进行处理,所有无需初始值
RDD行动算子
区分转换算子和行动算子:
转换算子会返回一个新的RDD,且属于懒执行
行动算子不会返回一个新的RDD,且每一个行动算子会触发一个job的提交运行
特殊情况:调用sortBy,和sortByKey,因为使用了RangeParitioner,在构造RangePartitoner时,调用抽样会触发Job提交
行动算子:
算子 | 说明 |
---|---|
reduce | 聚集RDD中的所有元素,先聚合分区内数据,再聚合分区间数据 |
collect | 以数组的形式将RDD中的元素返回到Driver端 |
count | 返回元素的个数 |
max | 返回元素的最大值 |
min | 返回元素的最小值 |
avg | 返回元素的的平均值 |
sum | 返回元素的和 |
first | 返回元素的第一个值 |
take (n) | 返回前n个元素 |
takeOrdered | 返回排序后的前n个元素 |
aggregate(zeroValue)(f1,f2) | 聚合,f1表示分区内运算,f2表示分区间运算;zeroValue不仅会在分区内参与运算,还会在分区间参与运算,在每个分区内加一次,最后分区间计算的时候再加一次 |
fold | aggregate简化版 |
countByValue | 对RDD中的每个元素的个数统计 |
countByKey | 对K-V类型的RDD中的每个key,统计k-v对的个数 |
foreach | 分布式遍历RDD中的每一个元素,每个元素调用一次函数 |
foreachPartition | 分布式遍历RDD中的每一个分区,每个分区的数据调用一次函数,适合数据的批量处理,比如写入数据库 |
saveAsTextFile() | 传一个路径,将RDD的数据写入该路径 |
注:foreach,重点是理解分布式遍历,也就是说foreach算子是在Executor上计算
RDD序列化
由于算子以外的代码都是在Driver端执行, 算子里面的代码都是在Executor端执行。而当出现闭包时,也就是说算子内调用了不属于该算子的变量,就要考虑这个变量能不能传送到Executor端执行的问题,换句话说就是我们要保证程序正常运行就要保证所有数据均可序列化。
RDD依赖关系
1)RDD的血缘关系:表示RDD产生的来龙去脉,追根溯源一样的血脉关系,可以通过.toDebugString方法查看
2)RDD的依赖关系:表示的是当前RDD与上一个RDD的关系,可以通过.dependencies方法查看
RDD持久化
缓存
在Spark中,可以将RDD的数据进行缓存!缓存后的RDD,在被其他行动算子执行时,可以直接从缓存中获取,避免根据血缘关系重新计算
使用: RDD.cache()
注意:
①缓存并不会改变RDD的血缘关系!原因因为缓存不可靠!在缓存丢失的情况下,行动算子依然需要根据血缘关系重建数据!
②shuffle操作会自动缓存数据
checkpoint
由于缓存的数据不可靠,可以使用RDD.checkpoint()将RDD中的数据以文件的形式保存在checkpoint目录中
注意:
①一旦RDD被checkpoint,血缘关系会被删除!
②checkpoint在第一个行动算子被调用时,触发,依然会生成一个Job,执行checkpoint。所以建议先cache,之后checkpoint就可以从cache中拿数据
RDD分区器
Spark目前支持Hash分区和Range分区,和用户自定义分区。Hash分区为当前的默认分区。分区器直接决定了RDD中分区的个数、RDD中每条数据经过Shuffle后进入哪个分区,进而决定了Reduce的个数。
只有K-V类型的RDD才有分区器,非Key-Value类型的RDD分区的值是None,而且分区数不一定需要分区器才能实现
①Hash分区:对于给定的key,计算其hashCode,并除以分区个数取余;
②Range分区:将一定范围内的数据映射到一个分区中,尽量保证每个分区数据均匀,而且分区间有序
广播变量
可以使用广播变量将一个只读的变量缓存在机器(Excutor)上,而不是每次都将这个变量的副本拷贝发送给每个Task
val broadcastVar=SparkContext.broadcast(v) //创建
broadcastVar.value // 访问广播变量
unpersist()// 释放Excutor上的广播变量
destroy() // 永久释放Excutor上的广播变量
注意:
①创建了广播变量,就要使用广播变量,而不是之前的数据
②广播变量不能被修改,必须是只读
使用场景: 在多个Task间共享一个大的只读变量
累加器
作用:累加器用来把Executor端变量信息聚合到Driver端。在Driver程序中定义的变量,在Executor端的每个Task都会得到这个变量的一份新的副本,每个task更新这些副本的值后,传回Driver端进行merge。
使用场景: 模拟 counter(计数器) 或 执行sum(累加求和)
Spark默认只提供数值类型(整数,浮点)的累加器,用户可以继承AccumulatorV2来自定义累加器!
SparkContext.longAccumulator()
SparkContext.doubleAccumulator()
累加器可以命名,可以在WEB UI界面查看不同阶段累加的明细!
使用: 调用add()方法在task端进行累加,但是在task端不能读取!
在Driver端,调用value()获取累加的值
运行流程:
1.累加器调用copy() 复制一个副本
2.调用reset() 将当前累加器的副本重置归0
3.调用isZero() 检查是否归零成功,如果返回false就报错!
4.返回true,序列化发送到Task
5.每个Task累加后,分别将累加器返回到Driver端,在Driver端执行merge()合并结果