spark总结

一、Spark概述:
Spark模块:

Core
SQL
Streaming
MLlib
Graphx

Spark VS MapReduce:

Spark比MapReduce更适合迭代式多任务计算:

    MapReduce多个作业间的数据通信是基于磁盘, 而Sparke多个作业间的数据通信是基于内存

    一个MapReduce程序只有map+reduce, 而Spark程序可以有多个算子

MapReduce是批处理, 而Spark是批处理 & 微批准实时处理

Shuffle中Spark不用落盘, 而MapReduce要磁盘

Spark有丰富的RDD算子, SQL操作, Streaming流处理, 还可以处理机器学习, 而MapReduce只有Mapper和Reducer

Spark有容错机制可以切断血缘, 避免失败后从源头数据开始全部重新计算

Spark 不能完全替代 MapReduce, 因为内存不充足时, Spark就无法工作了
架构:

Driver

是程序的入口, 是任务的调度者

功能:
    将用户程序转化为Job作业
    调度Task, 分配哪个Task由哪个Executor执行
    向Yarn申请Container资源
    监控Executor的执行情况

Executor

执行Task

对于Standalone独立部署模式, Master(相当于ResourceManager)和Worker(相当于NodeManager)来负责资源的管理调度

整个集群并行执行任务的数量称之为并行度

DAG 有向无环图, 是高度抽象后的 单向无闭环的任务流程图, 用于表示程序的拓扑结构
向Yarn提交Job:

Yarn Client模式(用于测试)

Driver模块的计算运行在本地

Yarn Cluster模式(生产环境)

Driver模块的计算运行在Yarn

    客户端向ResourceManager申请启动Driver(ApplicationMaster)

    ResourceManager分配Container, 在合适的NodeManager上启动Driver(ApplicationMaster)

    Driver(ApplicationMaster)向ResourceManager申请Executor需要的内存; ResourceManager进行分配Container, 然后在合适的NodeManager上启动Executor

    Executor进程启动后会向Driver反向注册, 当所有Executor全部注册完成后Driver开始执行main函数

    执行到Action算子时触发一个Job, 并根据宽依赖划分stage, 并生成对应的TaskSet, 之后将Task分配给Executor执行

二、Spark-Core:
概述:
三大数据结构:

RDD 弹性分布式数据集
累加器 分布式共享只写变量
广播变量 分布式共享只读变量

RDD算子的特点:

弹性

    容错的弹性, 有持久化机制, 数据丢失后可以自动恢复; 且可以切断血缘避免对父级的依赖, 减少重复计算

    计算的弹性, 计算失败后自动重试

    存储的弹性: 自动切换 内存和磁盘 去存储数据

    分片的弹性:可根据需要重新分片

    先将数据集分片, 然后将各个分片放到各个分区

分布式

数据集:RDD封装了计算逻辑,并不保存数据

数据抽象:RDD是一个抽象类,需要子类具体实现

不可变:RDD是不可变的, 要想改变只能产生新的RDD

可分区、各分区间是并行计算的

算子类型:

转换(Transform)算子, 并不会触发Job的执行
    Value类型
    双Value类型
    Key-Value类型

行动(Action)算子, 真正触发Job的执行

一、RDD算子:

算子以外的代码都是在Driver端执行, 算子里面的代码都是在Executor端执行
转换算子:

单Value类型:

map

以每条数据为单位将数据发到Executor端

rdd.map(num => num * 2)
    1

mapPartitions

以每个分区为单位将数据发到Executor端

rdd.mapPartitions(datas => datas.filter(_%2==0))
    1

mapPartitionsWithIndex

在mapPartitions基础上多了一个参数index, 即当前分区的索引序号

rdd.mapPartitions((index,datas) => datas.filter(index==0))
    1

flatMap

扁平化处理, 输入参数循环下来有多个List, 而输出结果只有一个List

val dataRDD = sparkContext.makeRDD(List(
    List(1,2),List(3,4)
),1)
// 计算结果 1,2,3,4
val dataRDD1 = dataRDD.flatMap(
    list => list
)


glom

将同一个分区的数据转换为同类型的数组

groupBy

会产生Shuffle, 数据被打乱分配到各个分区

一个组的数据在一个分区中, 一个分区中可以有多个组

filter

返回true/false来进行过滤

有些分区的数据过滤掉太多或太少的话, 可能会导致该分区发生数据倾斜

sample

根据一些规则进行随机抽取元素

    抽取数据不放回(伯努利算法)

    抽取数据放回(泊松算法)

distinct

去重

coalesce

缩减分区数量

会产生Shuffle

底层调的是repartition

repartition

扩大分区数量

会产生Shuffle

sortBy

会产生Shuffle

// 参数1 返回值是分区字段
// 参数2 是否升序
// 参数3 分区数量
dataRDD.sortBy(str=>str.subString(0,5), false, 4)
    1
    2
    3
    4

双Value类型:

intersection

对源RDD和参数RDD求交集后返回一个新的RDD

数据类型得相同

dataRDD1.intersection(dataRDD2)


union

数据类型得相同

对源RDD和参数RDD求并集后返回一个新的RDD

dataRDD1.union(dataRDD2)


subtract

数据类型得相同

求差集, dataRDD1-公共元素

val dataRDD1 = sparkContext.makeRDD(List(1,2,3,4))
val dataRDD2 = sparkContext.makeRDD(List(3,4,5,6))
// 结果为1,2
dataRDD1.subtract(dataRDD2)


zip

将两个RDD中的元素, 以键值对的形式进行合并

数据类型可以不同

两个RDD的分区数量得相等, 而且每个分区的数据个数也得相等才行, 否则会报错

Key-Value类型:

partitionBy

将数据按照指定Partitioner重新进行分区: partitionBy(partitioner: Partitioner)

groupByKey

reduceByKey

aggregateByKey

有每个分区的初始值(不算元素个数)

将数据进行分区内的计算和分区间的计算

// 每个分区内初始值(不算元素个数) & 分区内的计算规则 & 分区间的计算规则
dataRDD.aggregateByKey(0)(_+_ , _+_)
    1
    2

foldByKey

aggregateByKey分区内的计算规则和分区间的计算计算规则相同时, 可以简化为foldByKey

dataRDD.foldByKey(0)(_+_ )
    1

combineByKey

没有每个分区的初始值,

第一个参数表示将分区内的第一个数据转换结构, 第二个参数为分区内的计算规则, 第三个参数为分区间的计算规则

// 求每个key的平均值
val list: List[(String, Int)] = List(("a", 88), ("b", 95), ("a", 91), ("b", 93), ("a", 95), ("b", 98))
val input: RDD[(String, Int)] = sc.makeRDD(list, 2)
val combineRdd: RDD[(String, (Int, Int))] = input.combineByKey(
   v => (v, 1),    
   (acc: (Int, Int), v) => (acc._1 + v, acc._2 + 1),    
   (acc1: (Int, Int), acc2: (Int, Int)) => (acc1._1 + acc2._1, acc1._2 + acc2._2)
   )
// 再进行除操作


sortByKey

参数true/false代表是否升序排序

join

将(K,V) 组合 (K,W) 形成 (K,(V,W)), 二者的K类型得相同

leftOuterJoin

按key左外连接

cogroup

将(K,V) 组合 (K,W) 形成 (K,(Iterable<V>,Iterable<W>)), 二者的K类型得相同

行动算子:

reduce

collect

收集数据到Driver

count

统计RDD内元素的个数

first

take

takeOrdered

返回该RDD排序后的前n个元素

aggregate

分区内聚合计算要用到初始值, 分区间聚合计算也要用到初始值

fold

aggregate分区内的计算规则和分区间的计算计算规则相同时, 可以简化为fold

countByKey

统计每种key的个数

countByValue

统计每个元素value出现的个数, 这个value不是键值对的value, 而是单个元素的value

save相关算子

rdd.saveAsTextFile("textFile")rdd.saveAsObjectFile("objectFile")rdd.saveAsSequenceFile("sequenceFile")
    1

foreach

分布式遍历RDD中的每一个元素

会导致Shuffle的算子:

repartition操作:repartition、repartitionAndSortWithinPartitions、coalesce等
byKey操作: reduceByKey、groupByKey、sortByKey等
join操作: join、cogroup

大对比:

map VS mapPartitions:

    数据处理角度:
    map是分区内一个数据一个数据的执行, 而mapPartitions是以分区为单位进行批处理操作

    功能的角度
    map是一对一, 处理后数据不会增加也不会减少

    mapPartitions是一个集合对一个集合, 集合里可以增加或减少数据

    性能的角度
    mapPartitions类似于批处理, 所以性能较高;

    但是mapPartitions会长时间占用内存;

    所以内存不足时使用map, 充足时使用mapPartitions

groupByKey VS reduceByKey:

    功能上: groupByKey是分组, reduceByKey是分组后聚合
    从shuffle的角度: 二者都存在Shuffle;
        但是reduceByKey可以在Shuffle前对分区内相同key的数据进行预聚合, 从而减少落盘的数据量
        而groupByKey只是进行分组, 不存在数据量减少的问题, 从而不会减少Shuffle落盘的数据量

reduceByKey VS foldByKey VS aggregateByKey VS aggregate VS combineByKey:

    reduceByKey: 各个数据进行聚合, 没有分区内初始值, 分区内和分区间计算规则相同
    aggregateByKey: 分区内有初始值, 分区内和分区间计算规则不同
    foldByKey: 分区内有初始值, 分区内和分区间计算规则相同
    aggregate: 分区内聚合计算要用到初始值, 分区间聚合计算也要用到初始值, 分区内和分区间计算规则不同
    combineByKey: 将分区内的第一个数据转换数据结构, 分区内和分区间计算规则不相同

序列化:

分布式计算中, Driver要往Executor端发数据, 所以数据要支持序列化(算子内经常会用到算子外的数据, 闭包检测)
依赖关系:

RDD的Lineage(血统)会记录RDD间的元数据信息和转换行为, 当该RDD的部分分区数据丢失时 可以根据这些信息来恢复数据并重新计算

多个RDD间可能有血缘依赖, 后者RDD恢复数据时, 也需要前者RDD重新计算

窄依赖: 一个父(上游)RDD的Partition最多被子(下游)RDD的一个Partition使用, 像独生子女

宽依赖: 一个父(上游)RDD的Partition可以被子(下游)RDD的多个Partition使用(会产生Shuffle), 像多生子女; 又称Shuffle依赖

RDD 任务划分:

Application:初始化一个SparkContext即生成一个Application

Job:一个Action算子就会生成一个Job

Stage:Stage个数等于产生宽依赖(ShuffleDependency)的RDD个数+1(ResultStage)

即每一次Shuffle后, 都会新起一个Stage

Task:一个Stage阶段中最后一个RDD的分区个数就是Task的个数

Application->Job->Stage->Task每一层都是1对n的关系

Shuffle:

将上游各分区的数据打乱后 分到下游的各个分区, 即宽依赖

Shuffle要落盘, 因为得等待所有上游分区数据都到齐才能进行下一步操作, 所以Shuffle很耗时

窄依赖的话就不必等待所有分区数据全都到齐了, 故窄依赖不会引起Shuffle
持久化:

Cache缓存:

RDD通过cache( )方法将前面的计算结果临时缓存到内存

可以通过persist( )方法将其改为临时缓存到磁盘

并不会立刻执行, 而是遇到Action算子时才执行

Cache操作不会切断血缘依赖

因内存不足原因导致数据丢失时, 由于RDD的各个Partition是相对独立的, 所以只需要计算丢失的那部分Partition即可, 不必全部重新计算

Spark会自动对一些Shuffle操作的中间结果数据做持久化操作

这是为了避免当有一个节点计算失败了, 导致任务还需要重新从起点进行计算, 重新执行耗时的Shuffle

缓存是临时存储
CheckPoint检查点:

将RDD计算的中间数据写到磁盘

并不会立刻执行, 而是遇到Action算子时才执行
由于血缘依赖过长会造成容错成本过高, 检查点可以切断血缘关系, 避免从头到尾全部重新计算

检查点是长期存储

建议在checkpoint( )前先使用.cache( ), 这样做持久化操作时 只需从Cache缓存中读取数据即可, 否则需要重新计算一次RDD进行持久化

缓存和检查点区别:

Cache缓存只是将数据保存起来, 不切断血缘依赖; 而Checkpoint检查点切断血缘依赖
Cache缓存将数据存储在内存, 可靠性低, 但可以使用persist指定到磁盘; 而Checkpoint将数据存储到磁盘, 可靠性高
缓存是临时存储, 检查点是长期存储

缓存和检查点相同的应用场景:

为了复用前面RDD计算的中间结果, 避免大量的重复计算
依赖过长时, 避免后面的RDD计算出错后要从最初的RDD开始全部重新计算一遍

分区器:

只有Key-Value类型的RDD才有分区器,非Key-Value类型的RDD都分到None分区

Hash分区(默认) hash(key)%分区数量
Range分区 将一定范围内的数据分到一个分区中, 并且尽量使每个分区数据均匀, 分区内数据是有序的
自定义分区器

使用文件进行数据的读取和保存

文件格式:

text
csv
sequence(二进制文件)
Object(对象的序列化文件)

文件系统:

HDFS
HBase
本地磁盘

二、累加器:
为什么要有累加器?

各个Executor端计算的结果数据并不会影响到Driver端最终结果, 所以需要累加器
累加器的作用:

累加器用来把各个Executor端计算的结果数据聚合到Driver端
三、广播变量:
为什么要有广播变量?

Driver向Executor端的每个Task都发一份数据, 开销太大
广播变量的作用:

不需要给Executor端的每个Task都发一份数据, 而是只给Executor节点发一份数据即可
三、Spark-SQL:
HiveOnSpark:

计算引擎是是Spark, 语法是HiveSQL
SparkOnHive:

计算引擎是是Spark, 语法是SparkSQL
DataFrame:

是一个二维表格, 有一个个字段; 是弱类型
DataSet:

在DataFrame的基础上, 将字段映射为实体类的属性, 相当于多了表名; 是强类型

DataFrame=DataSet[ROW]
RDD & DataFrame & DataSet之间的转换:

在这里插入图片描述
数据存储格式:

SparkSQL默认读取和保存的文件格式为Parquet格式
四、Spark-Streaming:
Receiver:

其中一个Executor作Receiver接收数据

背压机制: 根据JobScheduler反馈作业的执行信息来动态调整Receiver数据接收率
状态:

状态就是一块内存, 如果要访问历史窗口(或批次)的数据时就需要用到状态, 把历史窗口(或批次)的数据处理结果值保存到状态里
无状态转化操作:

map, filter等

.transform(类似于RDD里的转换算子, 不会触发计算)转化为RDD进行操作

lineDStream.transform(rdd => {
val words: RDD[String] = rdd.flatMap(.split(" "))
val wordAndOne: RDD[(String, Int)] = words.map((
, 1))
val value: RDD[(String, Int)] = wordAndOne.reduceByKey(_ + _)
value
})

有状态转化操作:

状态操作需要设置检查点, 因为要用检查点来存状态数据

updateStateByKey:

// 定义更新状态方法,values为当前批次单词频度,state为之前批次单词频度
val updateFunc = (values: Seq[Int], state: Option[Int]) => {
  val currentCount = values.foldLeft(0)(_ + _)
  val previousCount = state.getOrElse(0)
  Some(currentCount + previousCount)
}

pairs.updateStateByKeyInt

Window操作:

window

开窗口, 窗口大小 & 滑动不长

reduceByWindow

窗口内做聚合

reduceByKeyAndWindow

窗口内按key做聚合

pairs.reduceByKeyAndWindow((a:Int,b:Int) => (a + b),Seconds(12), Seconds(6))
    1

reduceByKeyAndWindow

有状态操作, 为了避免窗口重叠部分的值的重复计算, 采用减去旧窗口不包含重叠部分的值,

pairs.reduceByKeyAndWindow(
  {(x, y) => x + y}, // 减去旧窗口不包含重叠部分的值
  {(x, y) => x - y},  // 增加新窗口不包含重叠部分的值
  Seconds(30),
  Seconds(10))
    1
    2
    3
    4
    5

countByWindow

统计窗口内数据的数量

countByValueAndWindow

统计窗口内每个元素出现了多少次

DStream输出:

类似于RDD的行动算子, 触发计算

print
foreach
foreachRDD
saveAsTextFiles
saveAsObjectFiles
saveAsHadoopFiles

注意:

Connection对象不能写在Driver层面, 因为Connection对象不能被序列化(安全起见), 而Driver发往Executor又非得把数据进行序列化

如果用foreach则每一条数据都使用一个Connection, 太浪费, 且最大连接数有限制

最好使用foreachPartition, 每个分区共用一个Connection

优雅关闭:

使用外部文件系统来控制内部程序关闭

//关闭时使用优雅关闭
sparkConf.set(“spark.streaming.stopGracefullyOnShutdown”, “true”)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值