Spark
一、Spark概况
(1)Hadoop和Spark的区别
hadoop | spark | |
---|---|---|
类型 | 基础平台:存储、计算、调度 | 分布式计算框架 |
场景 | 大规模数据集的批处理 | 迭代计算,交互式计算,流计算 |
价格 | 对机器相对要求比较低,廉价 | 对内存要求较高,相对较贵 |
编程范式 | Map+Reduce,API较为底层,算法适应性差 | RDD组成的DAG有环无向图,API较为顶层,方便实用 |
数据存储结构 | 存储在HDFS磁盘上,延迟大 | RDD中间运算结果存储在内存之中,延迟小 |
运行方式 | Task以进程方式进行维护,任务启动慢 | Task以线程方式维护,任务启动快 |
- 注1:Spark并不是完全的取代了Hadoop,主要取代的MapReduce的过程,其数据存储依旧可以使用HDFS,并且Spark可以借助Yarn实现资源的调度管理
- 注2:由于Hadoop的价格优势,在一些方面也是不能被spark取代
(2)Spark基本原理
- 设计原则:一个软件满足不同的计算场景,形成的Spark生态圈。其包括:Spark Core、Spark Streaming、Spark SQL、Spark MLLib和GraphX等组件,既能够提供内存计算框架、SQL即席查询、实时流式计算、机器学习和图计算等。
- 可以应对各种场景:批处理、交互式查询、流数据处理
(3) Spark概念和架构设计
-
RDD:弹性分布式和数据库
-
DAG:有向无环图
-
Excutor:运行在WorkerNode的一个进程,负责运行Task
-
Application:用户编写的Spark程序
-
Task:运行在Excutor的工作单元
-
Job:一个作业包含多个RDD作用于相应RDD撒花姑娘的各种操作
-
Stage:作业的基本调度单位
一个Application有一个Driver和若干个作业构成,一个作业由多个阶段构成,一个阶段由多个没有shuffle关系的任务组成
执行一个Application时,Driver会响集群管理器申请资源,启动Excutor,并向Excutor发送应用程序代码和文件,然后在Excutor上执行任务,运行结束之后,执行结果会返回给Driver,或者写到HDFS或者其他的数据库
(4)Spark运行流程
SparkContext对象代表了和一个集群的连接:
- 首先为Application构建起基本的运行环境,即由Driver创建一个SparkContext,进行资源的申请、任务的分配和监控;
- 资源管理器为Executor分配资源,并启动Executor进程;
- SparkContext根据RDD的依赖关系构建DAG图,DAG图提交给DAGScheduler解析成Stage,然后把一个个TaskSet提交给底层调度器TaskScheduler处理;Executor向SparkContext申请Task,Task Scheduler将Task发放给Executor运行,并提供应用程序代码;
- Task在Executor上运行,把执行结果反馈给TaskScheduler,然后反馈给DAGScheduler,运行完毕后写入数据并释放所有资源
二、Spark Core
1、RDD概述
(1)什么是RDD?为什么要有RDD?
- RDD(Resilient Distributed Dataset)叫做弹性分布式数据集,是Spark中最基本的数据抽象,代表一个不可变的、可分区的、里面的元素可并行计算的集合。
- –MapReduce的中间结果会写入到HDFS中,带来了大量的复制和序列化的开销,并且框架只能计算一些特定的计算模式。–RDD提供了一种抽象的数据模型,不同的RDD之间可以形成一个依赖关系,进而实现管道化,避免了中间数据的存储、磁盘IO和序列化的开销。
(2)RDD的属性
- 分区列表: 分区是数据集的基本单位,每一个分片会被指定一个Task,其决定并行度
- 计算函数:计算函数会被作用到每一个分区上
- 依赖关系:一个RDD依赖于其他多个RDD,可以通过流水一样的前后依赖关系找到计算部分丢失的数据(Spark容错机制)
- 分区函数:(默认是hash)可选,对于KV类型的RDD,会有一个Partitioner,即RDD的分区函数,默认是hashPartitioner
- 最佳位置:可选项,一个列表,存取每个Partition的优先位置信息(preffer location)。对于一个HDFS文件来说,这个列表就是每个Partition所在的块的信息。Saprk任务调度的时候按照**“移动数据不如移动计算”**的理念尽可能的选取有数据的Worker节点来进行任务计算。
(3)RDD算子
-
Transformation转换操作:返回一个新的RDD
-
Action动作操作:无返回值,或者返回其他类型
- RDD不是实际上的计算数据,只是记录数据位置,数据的转化关系
- RDD中的所有转化那都是惰性求值、延迟执行,只有在发生请求结果返回给Driver的Action动作时,这些转换才会正真的运行。
- 惰性求值、延迟执行可以在Action时对RDD操作形成DAG有向无环图进行Stage的划分和并行优化,是的Spark更加的高效
-
转换算子
转换算子 | 含义 |
---|---|
map(func) | 返回一个新的RDD,该RDD由每一个输入元素经过func函数转换后组成 |
filter(func) | 返回一个新的RDD,该RDD由经过func函数计算后返回值为true的输入元素组成 |
flatMap(func) | 类似于map,但是每一个输入元素可以被映射为0或多个输出元素(所以func应该返回一个序列,而不是单一元素) |
mapPartitions(func) | 类似于map,但独立地在RDD的每一个分片上运行,因此在类型为T的RDD上运行时,func的函数类型必须是Iterator[T] => Iterator[U] |
mapPartitionsWithIndex(func) | 类似于mapPartitions,但func带有一个整数参数表示分片的索引值,因此在类型为T的RDD上运行时,func的函数类型必须是(Int, Interator[T]) => Iterator[U] |
sample(withReplacement, fraction, seed) | 根据fraction指定的比例对数据进行采样,可以选择是否使用随机数进行替换,seed用于指定随机数生成器种子 |
union(otherDataset) | 对源RDD和参数RDD求并集后返回一个新的RDD |
intersection(otherDataset) | 对源RDD和参数RDD求交集后返回一个新的RDD |
distinct([numTasks])) | 对源RDD进行去重后返回一个新的RDD |
groupByKey([numTasks]) | 在一个(K,V)的RDD上调用,返回一个(K, Iterator[V])的RDD |
reduceByKey(func, [numTasks]) | 在一个(K,V)的RDD上调用,返回一个(K,V)的RDD,使用指定的reduce函数,将相同key的值聚合到一起,与groupByKey类似,reduce任务的个数可以通过第二个可选的参数来设置 |
aggregateByKey(zeroValue)(seqOp, combOp, [numTasks]) | 对PairRDD中相同的Key值进行聚合操作,在聚合过程中同样使用了一个中立的初始值。和aggregate函数类似,aggregateByKey返回值的类型不需要和RDD中value的类型一致 |
sortByKey([ascending], [numTasks]) | 在一个(K,V)的RDD上调用,K必须实现Ordered接口,返回一个按照key进行排序的(K,V)的RDD |
sortBy(func,[ascending], [numTasks]) | 与sortByKey类似,但是更灵活 |
join(otherDataset, [numTasks]) | 在类型为(K,V)和(K,W)的RDD上调用,返回一个相同key对应的所有元素对在一起的(K,(V,W))的RDD |
cogroup(otherDataset, [numTasks]) | 在类型为(K,V)和(K,W)的RDD上调用,返回一个(K,(Iterable,Iterable))类型的RDD |
cartesian(otherDataset) | 笛卡尔积 |
pipe(command, [envVars]) | 对rdd进行管道操作 |
coalesce(numPartitions) | 减少 RDD 的分区数到指定值。在过滤大量数据之后,可以执行此操作 |
repartition(numPartitions) | 重新给 RDD 分区 |
- 行动算子
动作算子 | 含义 |
---|---|
reduce(func) | 通过func函数聚集RDD中的所有元素,这个功能必须是可交换且可并联的 |
collect() | 在驱动程序中,以数组的形式返回数据集的所有元素 |
count() | 返回RDD的元素个数 |
first() | 返回RDD的第一个元素(类似于take(1)) |
take(n) | 返回一个由数据集的前n个元素组成的数组 |
takeSample(withReplacement,num, [seed]) | 返回一个数组,该数组由从数据集中随机采样的num个元素组成,可以选择是否用随机数替换不足的部分,seed用于指定随机数生成器种子 |
takeOrdered(n, [ordering]) | 返回自然顺序或者自定义顺序的前 n 个元素 |
saveAsTextFile(path) | 将数据集的元素以textfile的形式保存到HDFS文件系统或者其他支持的文件系统,对于每个元素,Spark将会调用toString方法,将它装换为文件中的文本 |
saveAsSequenceFile(path) | 将数据集中的元素以Hadoop sequencefile的格式保存到指定的目录下,可以使HDFS或者其他Hadoop支持的文件系统 |
saveAsObjectFile(path) | 将数据集的元素,以 Java 序列化的方式保存到指定的目录下 |
countByKey() | 针对(K,V)类型的RDD,返回一个(K,Int)的map,表示每一个key对应的元素个数 |
foreach(func) | 在数据集的每一个元素上,运行函数func进行更新 |
foreachPartition(func) | 在数据集的每一个分区上,运行函数func |
- 统计操作
算子 | 含义 |
---|---|
count | 个数 |
mean | 均值 |
sum | 求和 |
max | 最大值 |
min | 最小值 |
variance | 方差 |
sampleVariance | 从采样中计算方差 |
stdev | 标准差:衡量数据的离散程度 |
sampleStdev | 采样的标准差 |
stats | 查看统计结果 |
2、RDD的持久化/缓存
(1)persist方法和cache方法
-
RDD通过persist和cache将前面计算结果缓存,但是并不是调用此方法时,立即缓存,而是触发Action时,该RDD将会被缓存到计算节点的内存中,并提供后面重用。
-
参数中可以选择不同的存储级别,默认是MORY_ONLY。(此方法只写到内存中,内存不够时则不会缓存这些数据,每次需要是再重新计算)
-
一般情况下会使用MORY_AND_DISK,内存不足时会写到磁盘
-
实际开发中遇到一个RDD后面会被经常使用到或者是,运算时间比较长的就可以持久化。
3、RDD的容错机制checkpoint
-
持久化的局限性:持久化或者是缓存将数据写入到内存中,实际上最快但是也是最不可靠的,其次可以写到磁盘上,但是也不是完全可靠的
-
解决方案
Checkpoint的产生是为了更加可靠的数据持久化,在Checkpoint的时候通常将数据放到HDFS上,这就是天然的实现了数据上的安全,提高了RDD 的容错和高可用。
-
总结:
- 开发中保证数据的安全性和读取速率 :先使用cache/persist持久化,再做checkpoint
-
持久化和Checkpoint的区别
- 位置:持久化只能保存在本地的磁盘或者是内存中,Checkpoint可以保存到HDFS等可靠的存储上
- 生命周期:cache和persist的RDD会在程序结束之后被手动清除或是调用unpersist方法Checkpoint的RDD在程序结束后依旧存在,不会别被删除
4、RDD的依赖关系
- 两种类型:
- 宽依赖:父RDD的一个分区只会被子RDD的一个分区依赖;
- 窄依赖:父RDD的一个分区会被子RDD的多个分区依赖(涉及到shuffle)
- 为什么设计宽窄依赖
- 窄依赖
- 窄依赖的多个分区可以并行计算
- 窄依赖的一个分区的数据如果丢失,只需要重新计算对应的分区的数据就可以了
- 宽依赖
- 划分Stage(阶段)的依据:对于宽依赖,必须等待上一阶段的计算完成才能计算下一阶段
- 窄依赖
5、DAG的生成和划分Stage
(1)DAG相关
- 有向无环图DAG:原始RDD经过一系列的转化操作形成的DAG,任务执行时,可以按照DAG的描述,执行真正的计算(数据被操作的一个过程)
- 通过SparkContext创建的RDD开始—>触发Action结束形成一个完整的RDD
(2)DAG划分Stage
- 一个Spark中可能有多个DAG,每一个Action就是一个DAG
- 一个DAG分为多个Stage(根据宽依赖/shuffle进行划分)。
- 同一个Stage可以有多个Task并行执行
(3)为什么要划分Stage?
- 复杂的逻辑有shuffle,需要完成一个阶段再执行下面的程序。按照宽依赖/shuffle进行划分成多个Stage,同一个Stage中会有多个算子,可以形成pipeline流水线,流水线内的多个平行的分区可以并行执行。