Spark——RDD的内核调度

1、DAG定义

Spark的核心是根据RDD来实现的,SparkScheduler则为Spark核心实现的重要一环,其作用就是任务调度。Spark的任务调度就是如何组织任务去处理RDD中每个分区的数据,根据 RDD 的依赖关系构建 DAG,基于DAG划分Stage,将每个Stage中的任务发到指定节点运行。基于Spark的任务调度原理,可以合理规划资源利用,做到尽可能用最少的资源高效地完成任务计算。

以词频统计WordCount程序为例,DAG图:

在这里插入图片描述

DAG: 有向无环图,DAG有方向没有形成闭环的一个执行流程图。

  • 有向: 有方向;
  • 无环: 没有闭环。

1.1、Job和Action

Action: 返回值不是RDD的算子。它的作用是一个触发开关,会将action算子之前的一串rdd依赖链条执行起来。

结论

1个Action会产生1个DAG,如果在代码中有3个Action就产生3个DAG

即:一个Action产生的一个DAG,会在程序运行中产生一个JOB
所以:1个ACTION = 1个DAG = 1个JOB

如果一个代码中,写了3个Action,那么这个代码运行起来产生3个JOB,每个JOB有自己的DAG一个代码运行起来,在Spark中称之为 Application

层级关系: 1个Application中,可以有多个JOB每一个JOB内含一个DAG,同时每一个JOB都是由一个Action产生的。

1.2、DAG和分区

DAG是Spark代码的逻辑执行图,这个DAG的最终作用是;为了构建物理上的Spark详细执行计划而生。所以,由于Spark是分布式(多分区)的,那么DAG和分区之间也是有关联的。

# 假设,全部RDD都是3个分区在执行
rdd1=sc.textFile()
rdd2 =rdd1.flatMap()
rdd3=rdd2.map()
rdd4=rdd3.reduceByKey()
rdd4.action()

在这里插入图片描述

2、DAG的宽窄依赖和阶段划分

2.1、宽窄依赖

SparkRDD前后之间的关系,分为:

  • 窄依赖:RDD的一个分区,全部将数据发给子RDD的一个分区;

    在这里插入图片描述

    在这里插入图片描述

  • 宽依赖(shuffle):RDD的一个分区,将数据发给子RDD的多个分区。

    在这里插入图片描述

    在这里插入图片描述

2.2、阶段划分

对于Spark来说,会根据DAG,按照宽依赖,划分不同的DAG阶段

划分依据: 从后向前,遇到宽依赖就划分出一个阶段,称之为stage

如图,可以看到,在DAG中,基于宽依赖.将DAG划分成了2个stage;在stage的内部,一定都是:窄依赖

在这里插入图片描述

3、内存迭代计算

在这里插入图片描述

如图,基于带有分区的DAG以及阶段划分。可以从图中得到逻辑上最优的task分配一个task是一个线程来具体执行。那么如上图task1rdd1 rdd2 rdd3的迭代计算,都是由一个task(线程完成),这一阶段的这一条线,是纯内存计算。

如上图,task1 task2 task3,就形成了三个并行的内存计算管道。

Spark默认受到全局并行度的限制,除了个别算子有特殊分区情况,大部分的算子,都会遵循全局并行度的要求,来规划自己的分区数。如果全局并行度是3其实大部分算子分区都是3。

Spark我们一般推荐只设置全局并行度,不要再算子上设置并行度;除了一些排序算子外,计算算子就让他默认开分区就可以了。

面试题

3.1、Spark是怎么做内存计算的?DAG的作用?Stage阶段划分的作用?

  • Spark会产生DAG图;
  • DAG图会基于分区和宽窄依赖关系划分阶段;
  • 一个阶段的内部都是窄依赖,窄依赖内,如果形成前后1:1的分区对应关系,就可以产生许多内存迭代计算的管道;
  • 这些内存选代计算的管道,就是一个个具体的执行Task
  • 一个Task是一个具体的线程,任务跑在一个线程内,就是走内存计算了。

3.2、Spark为什么比MapReduce快?

  • Spark的算子丰富,MapReduce算子匮乏(MapReduce),MapReduce这个编程模型,很难在一套MR中处理复杂的任务;很多的复杂任务,是需要写多个MapReduce进行串联多个MR串联通过磁盘交互数据。
  • Spark可以执行内存迭代,算子之间形成DAG基于依赖划分阶段后,在阶段内形成内存迭代管道,但是MapReduceMapReduce之间的交互依旧是通过硬盘来交互的。

编程模型上Spark占优(算子够多);
算子交互上和计算上可以尽量多的内存计算而非磁盘迭代。

4、Spark并行度

Spark的并行: 在同一时间内,有多少个task在同时运行;
并行度: 并行能力的设置。

比如设置并行度6其实就是要6个task并行在跑,在有了6个task并行的前提下,rdd的分区就被规划成6个分区了。

4.1、如何设置并行度

可以在代码中和配置文件中以及提交程序的客户端参数中设置,优先级从高到低:

  • 代码中

  • 客户端提交参数中

  • 配置文件中

  • 默认(1,但是不会全部以1来跑,多数时候基于读取文件的分片数量来作为默认并行度)

全局并行度配置的参数:spark.default.parallelism

4.2、全局并行度

  • 配置文件中:
# conf/spark-defaults.conf中设置
spark.default.parallelism 100
  • 在客户端提交参数中:
bin/spark-submit --conf "spark.default.parallelism=100"
  • 在代码中设置:
conf = SaprkConf()
conf.set("spark.default.parallelism", "100")

全局并行度是推荐设置,不要针对RDD改分区,可能会影响内存迭代管道的构建,或者会产生额外的Shuffle

4.3、针对RDD的并行设置(不推荐)

只能在代码写,算子:

  • repartition算子
  • coalesce算子
  • partitionBy算子

4.4、集群中如何规划并行度

结论: 设置为 CPU 总核心的2 ~ 10倍,比如集群可用CPU核心是100个,我们建议并行度是200~ 1000。

确保是CPU核心的整数倍即可,最小是2倍,最大一般10倍或更高(适量)均可。

4.5、为什么要设置至少2倍?

  • CPU的一个核心一时间只能干一件事情;
  • 所以,在100个核心的情况下,设置100个并行,就能让CPU 100%出力;
  • 这种设置下,如果task的压力不均衡,某个task先执行完了。就导致某个CPU核心空闲;
  • 所以,我们将Task(并行)分配的数量变多,比如800个并行,同一时间只有100个在运行,700个在等待;
  • 但是可以确保,某个task运行完了。后续有task补上,不让cpu闲下来,最大程度利用集群的资源。

规划并行度,只看集群总CPU核数。

5、Spark的任务调度

5.1、Spark程序调度流程

在这里插入图片描述

  • Driver被构建出来;
  • 构建SparkContext(执行环境入口对象);
  • 基于DAG SchedulerDAG调度器)构建逻辑Task分配;
  • 基于TaskSchedulerTask调度器)将逻辑Task分配到各个Executor上干活,并监控它们;
  • Worker(Executor),被TaskScheduler管理监控,听从它们的指令干活,并定期汇报进度。

1,2,3,4都是Driver的工作,5是Worker的工作。

5.2、Driver调度过程

  • 逻辑DAG产生;
  • 分区DAG产生;
  • Task划分;
  • Task分配给Executor并监控其工作。

5.3、Driver内的两个组件

  • DAG调度器: 将逻辑的DAG图进行处理,最终得到逻辑上的Task划分;
  • Task调度器: 基于DAG Scheduler的产出,来规划这些逻辑的task,应该在哪些物理的executor上运行,以及监控管理它们的运行。

6、Spark层次关系

  • 一个Spark环境可以运行多个Application
  • 一个代码运行起来,会成为一个Application
  • Application内部可以有多个Job
  • 每个Job由一个Action产生, 并且每个Job有自己的DAG执行图;
  • 一个JobDAG图会基于宽窄依赖划分成不同的阶段;
  • 不同阶段内基于分区数量,形成多个并行的内存迭代管道;
  • 每一个内存迭代管道形成一个TaskDAG调度器划分将Job内划分出具体的task任务,一个Job被划分出来的task在逻辑上称之为这个jobtaskset )。

7、Spark Shuffle

Spark在DAG调度阶段会将一个Job划分为多 个Stage,上游Stagemap工作, 下游Stagereduce工作,其本质上还是MapReduce计算框架。Shuffle是连接mapreduce之间的桥梁,它将 map 的输出对应到 reduce 输入中, 涉及到序列化反序列化、跨节点网络IO以及磁盘读写IO等。

在这里插入图片描述

Spark的Shuffle分为WriteRead两个阶段,分属于两个不同的Stage,前者是ParentStage的最后一步,后者是Child Stage的第一步。

执行Shuffle的主体是Stage中的并发任务,这些任务分ShuffleMapTaskResultTask两种,ShuffleMapTask要进行ShuffleResultTask负责返回计算结果,一个Job中只有最后的Stage采用ResultTask,其他的均为ShuffleMapTask。如果要按照map端和reduce端来分析的话,ShuffleMapTask可以即是map端任务,又是reduce端任务,因为Spark中的Shuffle是可以串行的;ResultTask则只能充当reduce端任务的角色。

在这里插入图片描述

8、Hash Shuffle

8.1、阶段划分

  • shuffle write:mapper阶段,上一个stage得到最后的结果写出;
  • shuffle read:reduce阶段,下一个stage拉取上一个stage进行合并

未经优化的 hashShuffleManager HashShuffle是根据task的计算结果的key值的hashcode%ReduceTask来决定放入哪一个区分,这样保证相同的数据一定放入一个分区,Hash Shuffle过程如下:

在这里插入图片描述

根据下游的task决定生成几个文件,先生成缓冲区文件在写入磁盘文件,再将block文件进行合并。未经优化的shuffle write操作所产生的磁盘文件的数量是极其惊人的。提出如下解决方案。

经过优化的 hashShuffleManager shuffle write过程中,task就不是为下游stage的每个task创建一个磁盘文件了。此时会出现shuffleFileGroup的概念,每个shuffleFileGroup会对应一批磁盘文件,每一个Group磁盘文件的数量与下游stagetask数量是相同的。
在这里插入图片描述

8.2、优化前后对比

  • 未经优化:

    • 上游的task数量:m
    • 下游的task数量:n
    • 上游的executor数量:k (m>=k)
    • 总共的磁盘文件:mn
  • *优化之后的:

    • 上游的task数量:m
    • 下游的task数量:n
    • 上游的executor数量:k (m>=k)
    • 总共的磁盘文件:kn

9、Sort Shuffle Manager

SortShuffleManager的运行机制主要分成两种,一种是普通运行机制,另一种是bypass运行机制。当shuffle write task的数量小于等于spark.shuffle.sort.bypassMergeThreshold参数的值时(默认为200),就会启用bypass机制。

在这里插入图片描述

  • 该模式下,数据会先写入一个内存数据结构中(默认5M),此时根据不同的shuffle算子,可能选用不同的数据结构。如果是reduceByKey这种聚合类的shuffle算子,那么会选用Map数据结构,一边通过Map进行聚合,一边写入内存;如果是join这种普通的shuffle算子,那么会选用Array数据结构,直接写入内存。

  • 每写一条数据进入内存数据结构之后,就会判断一下,是否达到了某个临界阈值。如果达到临界阈值的话,那么就会尝试将内存数据结构中的数据溢写到磁盘,然后清空内存数据结构。

  • 排序: 在溢写到磁盘文件之前,会先根据key对内存数据结构中已有的数据进行排序。

  • 溢写: 排序过后,会分批将数据写入磁盘文件。默认的batch数量是10000条,也就是说,排序好的数据,会以每批1万条数据的形式分批写入磁盘文件。

  • merge: 一个task将所有数据写入内存数据结构的过程中,会发生多次磁盘溢写操作,也就会产生多个临时文件。最后会将之前所有的临时磁盘文件都进行合并成1个磁盘文件,这就是merge过程。

由于一个task就只对应一个磁盘文件,也就意味着该taskReduce端的stagetask准备的数据都在这一个文件中,因此还会单独写一份索引文件,其中标识了下游各个task的数据在文件中的start offsetend offset

9.1、bypass运行机制

bypass运行机制的触发条件如下:

  • shuffle map task数量小于spark.shuffle.sort.bypassMergeThreshold=200参数的值。
  • 不是map combine聚合的shuffle算子(比如reduceByKeymap combie)。

在这里插入图片描述

  • 此时task会为每个reduce端的task都创建一个临时磁盘文件,并将数据按key进行hash,然后根据keyhash值,将key写入对应的磁盘文件之中。当然,写入磁盘文件时也是先写入内存缓冲,缓冲写满之后再溢写到磁盘文件的。最后,同样会将所有临时磁盘文件都合并成一个磁盘文件,并创建一个单独的索引文件。
  • 该过程的磁盘写机制其实跟未经优化的HashShuffleManager是一模一样的,因为都要创建数量惊人的磁盘文件,只是在最后会做一个磁盘文件的合并而已。因此少量的最终磁盘文件,也让该机制相对未经优化的HashShuffleManager来说,shuffle read的性能会更好。

而该机制与普通SortShuffleManager运行机制的不同在于:

  • 磁盘写机制不同;

  • 不会进行排序。也就是说,启用该机制的最大好处在于,shuffle write过程中,不需要进行数据的排序操作,也就节省掉了这部分的性能开销。

9.2、总结

  • SortShuffle也分为普通机制和bypass机制
  • 普通机制在内存数据结构(默认为5M)完成排序,会产生2M个磁盘小文件。
  • 而当shuffle map task数量小于spark.shuffle.sort.bypassMergeThreshold参数的值。或者算子不是聚合类的shuffle算子(比如reduceByKey)的时候会触发SortShufflebypass机制,SortShuffle的bypass机制不会进行排序,极大的提高了其性能。

10、Shuffle的配置选项

Shuffle阶段划分:

  • shuffle write:mapper阶段,上一个stage得到最后的结果写出;
  • shuffle read:reduce阶段,下一个stage拉取上一个stage进行合并。

细节

Spark 的shuffle调优: 主要是调整缓冲的大小,拉取次数重试重试次数与等待时间,内存比例分配,是否进行排序操作等等。

  • spark.shuffle.file.buffer

    • 参数说明: 该参数用于设置shuffle write taskBufferedOutputStreambuffer缓冲大小(默认是32K)。将数据写到磁盘文件之前,会先写入buffer缓冲中,待缓冲写满之后,才会溢写到磁盘;
    • 调优建议: 如果作业可用的内存资源较为充足的话,可以适当增加这个参数的大小(比如64k),从而减少shuffle write过程中溢写磁盘文件的次数,也就可以减少磁盘IO次数,进而提升性能。在实践中发现,合理调节该参数,性能会有1%~5%的提升。
  • spark.reducer.maxSizeInFlight

    • 参数说明: 该参数用于设置shuffle read task的buffer缓冲大小,而这个buffer缓冲决定了每次能够拉取多少数据。(默认48M);
    • 调优建议: 如果作业可用的内存资源较为充足的话,可以适当增加这个参数的大小(比如96m),从而减少拉取数据的次数,也就可以减少网络传输的次数,进而提升性能。在实践中发现,合理调节该参数,性能会有1%~5%的提升。
  • spark.shuffle.io.maxRetries and spark.shuffle.io.retryWait

    • spark.shuffle.io.maxRetries :shuffle read taskshuffle write task所在节点拉取属于自己的数据时,如果因为网络异常导致拉取失败,是会自动进行重试的。该参数就代表了可以重试的最大次数。(默认是3次);
    • spark.shuffle.io.retryWait: 该参数代表了每次重试拉取数据的等待间隔。(默认为5s);
    • 调优建议: 一般的调优都是将重试次数调高,不调整时间间隔。
  • spark.shuffle.memoryFraction

    • 参数说明: 该参数代表了Executor内存中,分配给shuffle read task进行聚合操作内存比例。
  • spark.shuffle.manager

    • 参数说明: 该参数用于设置shufflemanager的类型(默认为sort)。Spark1.5x以后有三个可选项:
      • Hash:spark1.x版本的默认值,HashShuffleManager
      • Sort:spark2.x版本的默认值,普通机制,当shuffle read task 的数量小于等于。spark.shuffle.sort.bypassMergeThreshold参数,自动开启bypass机制
  • spark.shuffle.sort.bypassMergeThreshold

    • 参数说明:ShuffleManagerSortShuffleManager时,如果shuffle read task的数量小于这个阈值(默认是200),则shuffle write过程中不会进行排序操作;
    • 调优建议: 当你使用SortShuffleManager时,如果的确不需要排序操作,那么建议将这个参数调大一些。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值