【Spark】Spark Core 高级特性

1、Spark优化

(1)代码优化
1)如果一个RDD只使用一次,那么不赋值,直接转换操作,这叫做链式编程。
2)对于多次使用的RDD,需要对rdd进行cache操作(使用完成后,需要释放)。
3)优先选择reduceByKey和aggregateByKey替代groupByKey,原因是:groupByKey可能导致OOM异常,性能没有前两个API好(前两个API存在combiner操作)。
(2)资源优化:Spark的内存管理机制和动态资源分配。
(3)数据倾斜优化
数据倾斜原因:数据重复分配不均匀导致的,可能会导致某些task执行速度比较慢或者出现OOM异常。
解决方法:
1)更改分区策略(机制<自定义数据分区器>+分区数)。
2)两阶段聚合。
(4)两阶段聚合
适用场景:对RDD进行分组操作的时候,某些Task处理数据过多或者产生OOM异常等情况。
实现思路:第一阶段给每个key加一个随机数,然后进行局部的聚合操作;第二阶段去除每个key的前缀,然后进行全局的聚合操作。
实现原理:将key添加随机前缀的方式可以让一个key变成多个key,可以让原本被一个task处理的数据分布到多个task上去进行局部的聚合,进而解决单个task处理数据太多的问题;随后去掉前缀,进行全局集合,完成功能的实现。
优缺点:对于聚合类shuffle操作(groupByKey、reduceByKey等)产生的问题能够很好的解决;但是对于非聚合类shuffle操作(join等)产生的问题很难使用该方式解决。
两阶段聚合原理图:

2、Spark Core 容错

Spark应用程序的高可用性主要包含两个部分:集群环境的高可用以及应用程序的容错特性;集群环境的高可用,主要由集群框架来控制,比如Spark on Yarn模式下的ResourceManager的HA、Spark Standalone模式下的Master的HA等特性的设置来保障集群的高可用特性;至于应用程序的容错需要考虑应用的各个组成部分的容错。
Spark应用程序执行过程中,一般存在以下执行失败的情况:
Driver进程宕机:driver运行机器宕机、driver程序运行过程中异常导致进程宕机等。
Executor进程宕机:比如executor进程所在的机器(work)宕机、Executor和Driver之间通信超时。
Task执行失败:Task任务执行线程执行过程中产生异常导致Task执行失败。
(1)driver宕机
运维监控机器是否存活,如果机器宕机,运维人员重启服务机器及spark应用。
运维通过Spark Job的History服务,监控应用是否执行成功,如果执行失败,通知开发人员,应用执行失败,重启服务即可。
SparkStreaming中,重启Spark应用后,可用通过checkpointDir进行Job数据恢复。
1)client:程序直接挂了;
2)cluster:
a. spark on standalone/mesos:通过spark-submit的参数–supervise可以指定当driver宕机的时候,在其他的节点上重新恢复。
b. spark on yarn:自动恢复四次。
(2)executor宕机
Spark应用程序的Driver会自动从存活的work机器列表中选择一台机器重新启动executor进程,并将原来executor中执行失败的Task重新执行;并将原来的executor从Driver中的Executors列表中移除。
(3)task执行失败
1)Spark程序会进行Task重试机制,如果某个Task失败重试次数超过3次(spark.task.maxFailures,默认参数为4)后,当前Job执行失败(即当前job对应的action API调用在driver中抛出异常);local模式默认不开启Task重试机制,local模式下spark程序运行的重启次数不是通过参数spark.task.maxFailures来进行控制的。
2)Task数据恢复/重新运行的机制实质上是RDD的容错机制,即Lineage机制;RDD的Lineage机制记录的是粗颗粒度的特定数据Transformation操作(如filter、map等)行为。当这个RDD的部分分区数据丢失时,它可以通过Lineage获取足够的信息来重新运算和恢复丢失的数据分区;该机制体现在RDD上就是RDD依赖特性。
3)如果RDD的Lineage生命线特别长,此时某些Task执行失败的恢复成本就会比较高,那么可以采用检查点(checkpoint)或者缓存(cache)的方式将数据冗余的保存下来,当检查点/缓存点之后的rdd的task出现异常的时候,可以直接从检查点重新构建Lineage,可以减少执行开销。
(4)如果后续rdd执行过程中,出现数据丢失,容错的方式为:rdd lineage(生命线),即RDD的依赖提供的一种容错机制,当子RDD执行失败的时候,可以直接从父RDD进行恢复操作;如果父RDD的执行结果进行了缓存操作,子RDD直接从缓存位置获取结果数据;如果cache的不是全部数据的话,那么部分数据从缓存中读取,其它数据从父RDD的数据来源读取(会存在父RDD的代码逻辑的执行);如果子RDD失败的是单个分区,那么如果父rdd和子rdd的关系是窄依赖,只需要恢复父rdd对应分区的数据即可,如果关系是宽依赖,需要将所有父rdd的数据都执行一遍。

3、Spark应用的组成

Driver + Executors
Driver:SparkContext上下文的构建、RDD的构建、RDD的调度。
Executor:具体task执行的位置。
一个application中可以存在多个job,一个job可以存在多个stage,一个stage 可以存在多个task。
Job的产生:由于调用了RDD的Action类型的API,所以触发rdd对应的job提交到executors中执行。
Stage:当RDD的DAG图进行提交之前,Driver中的SparkContext中的DAGScheduler会DAG进行划分,形成Stage;划分规则:从DAG图的最后往前推,直到遇到一个宽依赖的API,那么就形成一个Stage,继续直到第一个RDD。Stage的执行是有依赖关系的,前一个Stage的数据结果是后一个Stage的数据的输入;只有上一个Stage中的所有task都执行完了下一个Stage才会执行。
Task:是Executor中执行的最小单位。task实质上就是分区,一个分区的数据的代码执行就是一个Task。
分区是从数据的分布情况来讲,task是从数据的执行逻辑情况来讲。
每个Task中的执行逻辑是一样的,只有处理的数据不一样,代码逻辑其实就是RDD的API组成的一个执行链。

4、Spark提交流程

(1)RDD调用transformation类型的API形成RDD的DAG执行图。
(2)RDD调用action类型的API触发job执行的提交操作。
(3)SparkContext中的DAGScheduler对RDD的DAG执行图进行Stage的划分。
(4)SparkContext中的TaskScheduler对Stage进行task任务提交执行,将task提交到executor中执行(进行调度操作)。
(5)等待task执行完成,当一个stage的所有task均执行完成后,开始下一个stage的调度执行,直到job执行完成。
Spark应用的执行过程:
Driver + Executor
Spark On Yarn执行方式中:
client模式:
driver:负责applicationmaster的资源申请和任务调度。
applicationMaster:负责Executor中的资源申请。
Executor:负责Task的执行。
cluster模式:
dirver(ApplicationMaster):负责资源的申请和任务的调度。
Executor:负责Task执行。

5、Spark Shuffle

Spark Shuffle只存在于RDD的宽依赖中,有一个宽依赖就一个shuffle过程,由Spark Shuffle Manager进行管理,参数为spark.shuffle.manager:sort。
参考文献:http://spark.apache.org/docs/1.6.1/configuration.html#shuffle-behavior
Shuffle优化:
(1)Spark Shuffle Manager:sort
当task的数量小于200的时候,会自动启动by_pass模式(没有数据排序的操作)
spark.shuffle.sort.bypassMergeThreshold:200
(2)Spark Shuffle Manager:hash
当应用中的数据不需要进行排序的时候,可以直接考虑使用hash shuffle manager;当使用hash shuffle manager的时候(当分区数比较多的),需要将参数:spark.shuffle.consolidateFiles设置为true,表示开启文件合并功能。

6、Spark Scheduler

参考文献:
http://spark.apache.org/docs/1.6.1/configuration.html#scheduling
http://spark.apache.org/docs/1.6.1/job-scheduling.html
Spark的job调度分为FIFO(先进先出)和FAIR(公平调度)
FIFO:按照提交的时间顺序执行job任务;
FAIR:并行执行job任务,按照自愿的需求量进行分配。
注意:在某些场景下,将调度策略改为FAIR有一定的执行效率的提升,如果一个job执行的时间比较长,但是资源没有得到充足的利用,而且还有后续没有依赖的job需要执行的情况; 一般建议为FIFO。

7、共享变量

参考文献:http://spark.apache.org/docs/1.6.1/programming-guide.html#shared-variables
默认情况下,如果在一个算子的函数中使用到了某个外部的变量,那么这个变量的值会被拷贝到每个task中。此时每个task只能操作自己的那份变量副本。如果多个task想要共享某个变量,那么这种方式是做不到的。
Spark为此提供了两种共享变量,一种是Broadcast Variable(广播变量),另一种是Accumulator(累加变量)。Broadcast Variable会将使用到的变量,仅仅为每个节点拷贝一份,更大的用处是优化性能,减少网络传输以及内存消耗。Accumulator则可以让多个task共同操作一份变量,主要可以进行累加操作。
(1)广播变量(broadcast variables)
Spark提供的Broadcast Variable,是只读的。并且在每个节点上只会有一份副本,而不会为每个task都拷贝一份副本。因此其最大作用,就是减少变量到各个节点的网络传输消耗,以及在各个节点上的内存消耗。此外,spark自己内部也使用了高效的广播算法来减少网络消耗。
可以通过调用SparkContext的broadcast()方法,来针对某个变量创建广播变量。然后在算子的函数内,使用到广播变量时,每个节点只会拷贝一份副本了。每个节点可以使用广播变量的value()方法获取值。记住,广播变量,是只读的。

val factor = 3
val factorBroadcast = sc.broadcast(factor)
val arr = Array(1, 2, 3, 4, 5)
val rdd = sc.parallelize(arr)
val multipleRdd = rdd.map(num => num * factorBroadcast.value())
multipleRdd.foreach(num => println(num))

参考文献:http://spark.apache.org/docs/1.6.1/programming-guide.html#broadcast-variables
功能:减少driver到executor的数据传输量,可以通过广播变量实现map join。注意:

  1. 广播变量一经广播,变量不允许被修改
  2. 广播变量在executor中的存储在storage memory部分,如果task在运行的过程中发现storage memory中不存在对应的值,会重新从driver中获取2. 广播变量在executor中的存储在storage memory部分,如果task在运行的过程中发现storage memory中不存在对应的值,会重新从driver中获取。
  3. 如果广播变量不用了记住删除清空操作。

(2)累加器(accumulators)
Spark提供的Accumulator,主要用于多个节点对一个变量进行共享性的操作。Accumulator只提供了累加的功能。但是确给我们提供了多个task对一个变量并行操作的功能。但是task只能对Accumulator进行累加操作,不能读取它的值。只有Driver程序可以读取Accumulator的值。

val sumAccumulator = sc.accumulator(0)
val arr = Array(1, 2, 3, 4, 5)
val rdd = sc.parallelize(arr)
rdd.foreach(num => sumAccumulator += num)
println(sumAccumulator.value)

注意:累加器是在executor中进行数据累加操作,在driver中进行数据读取操作(executor中不允许数据读取操作)。
Accumulators的实现机制:基于AccumulableParam提供的方法进行数据的聚合操作,步骤为:先在每个分区中进行数据的累加操作,然后当分区执行完后,每个分区的数据集合再进行合并操作得到最终结果。Spark的Accumulators默认仅支持数字类型和Vector类型的RDD数据累加。
共享变量的工作原理:

发布了219 篇原创文章 · 获赞 603 · 访问量 129万+

猜你喜欢

转载自blog.csdn.net/gongxifacai_believe/article/details/86715204