Scala | 宽窄依赖 | 资源调度与任务调度 | 共享变量 | SparkShuffle | 内存管理

文章目录

    • 一、术语解释
    • 二、窄依赖和宽依赖
      • 1.窄依赖
      • 2.宽依赖
      • 3.宽窄依赖图理解(以WordCount为例)
    • 三、Stage详解
      • 1.stage 切割规则
      • 2.stage 计算模式
    • 四、 Spark 资源调度和任务调度
      • 1.Spark 资源调度和任务调度的流程
      • 2.资源调度分析
      • 3.任务调度分析
      • 4.粗粒度资源申请和细粒度资源申请
        • 4.1 粗粒度资源申请(Spark)
        • 4.2 细粒度资源申请(MR)
    • 五、共享变量
      • 1.广播变量
      • 2.累加器
    • 六、SparkShuffle
      • 1.SparkShuffle 概念
      • 2.HashShuffle
      • 3.SortShuffle
      • 4.Shuffle文件寻址
      • 5.Shuffle 参数调优
    • 七、Spark内存管理


  • 课程地址:spark讲解
  • Scala | Spark基础入门 | IDEA配置 | 集群搭建与测试
  • Scala | Spark核心编程 | SparkCore | 算子
  • Scala | 宽窄依赖 | 资源调度与任务调度 | 共享变量 | SparkShuffle | 内存管理
  • Scala | SparkSQL | 创建DataSet | 序列化问题 | UDF与UDAF | 开窗函数

一、术语解释

Scala | 宽窄依赖 | 资源调度与任务调度 | 共享变量 | SparkShuffle | 内存管理_第1张图片

Task的个数 = Paration的个数 = 并行度
job串行,Stage串行加并行,task并行;
app > job > stage > task

二、窄依赖和宽依赖

  RDD 之间有一系列的依赖关系,依赖关系又分为窄依赖宽依赖

1.窄依赖

  父 RDD 和子 RDD partition 之间的关系是一对一的。或者父 RDD 和子 RDDpartition 关系是多对一的。不会有 shuffle 的产生。

shuffle会产生数据重新分布

2.宽依赖

  父 RDD 与子 RDD partition 之间的关系是一对多。会有 shuffle 的产生。

3.宽窄依赖图理解(以WordCount为例)

Scala | 宽窄依赖 | 资源调度与任务调度 | 共享变量 | SparkShuffle | 内存管理_第2张图片
Scala | 宽窄依赖 | 资源调度与任务调度 | 共享变量 | SparkShuffle | 内存管理_第3张图片

三、Stage详解

  Spark 任务会根据 RDD 之间的依赖关系,形成一个 DAG 有向无环图,DAG会提交给 DAGSchedulerDAGScheduler 会把 DAG 划分相互依赖的多个stage,划分 stage 的依据就是 RDD 之间的宽窄依赖。遇到宽依赖就划分stage,每个 stage 包含一个或多个 task 任务。然后将这些 tasktaskSet的形式提交给 TaskScheduler 运行。stage是由一组并行的 task 组成。

1.stage 切割规则

  切割规则:从后往前,遇到宽依赖就切割 stage
Scala | 宽窄依赖 | 资源调度与任务调度 | 共享变量 | SparkShuffle | 内存管理_第4张图片

2.stage 计算模式

  stage 计算模式是pipeline 管道计算模式。pipeline 只是一种计算思想、模式。
Scala | 宽窄依赖 | 资源调度与任务调度 | 共享变量 | SparkShuffle | 内存管理_第5张图片
  测试验证 pipeline 计算模式:

package com.shsxt.scalaTest.core.transform_operator

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

object Pipeline_Test {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf()
    conf.setMaster("local").setAppName("pipeline")
    val sc = new SparkContext(conf)

    val rdd: RDD[Int] = sc.parallelize(Array(1, 2, 3))

    val rdd2: RDD[Int] = rdd.map(x => {
      println("map---------------", x)
      x
    })

    val rdd3: RDD[Boolean] = rdd2.map(x => {
      println("filter-------------", x)
      true
    })

    rdd3.collect()

    sc.stop()
  }
}
(map---------------,1)
(filter-------------,1)
(map---------------,2)
(filter-------------,2)
(map---------------,3)
(filter-------------,3)

注意点

  1. 数据一直在管道里面什么时候数据会落地?
    • RDD 进行持久化
    • shuffle write 的时候
  2. Stagetask 并行度是由 stage 的最后一个 RDD 的分区数来决定的 。
  3. 如何改变 RDD 的分区数?

    例如:reduceByKey(XXX,3)GroupByKey(4)

四、 Spark 资源调度和任务调度

1.Spark 资源调度和任务调度的流程

Scala | 宽窄依赖 | 资源调度与任务调度 | 共享变量 | SparkShuffle | 内存管理_第6张图片
  启动集群后,Worker 节点会向 Master 节点汇报资源情况,Master 掌握了集群资源情况。当 Spark 提交一个 Application 后,根据 RDD 之间的依赖关系将 Application 形成一个 DAG 有向无环图。任务提交后,Spark 会在Driver 端创建两个对象:DAGSchedulerTaskSchedulerDAGScheduler是任务调度的高层调度器,是一个对象。
  DAGScheduler 的主要作用就是将DAG 根据 RDD 之间的宽窄依赖关系划分为一个个的 Stage,然后将这些StageTaskSet 的形式提交给 TaskScheduleTaskScheduler 是任务调度的底层调度器,这里 TaskSet 其实就是一个集合,里面封装的就是一个个的 task 任务,也就是 stage 中的并行度 task 任务)。
  TaskSchedule 会遍历TaskSet 集合,拿到每个 task 后会将 task 发送到计算节点 Executor 中去执行(其实就是发送到 Executor 中的线程池 ThreadPool 去执行)。
  taskExecutor 线程池中的运行情况会向 TaskScheduler 反馈,当 task 执行失败时,则由 TaskScheduler 负责重试,将 task 重新发送给 Executor 去执行,默认重试 3 次。如果重试 3 次依然失败,那么这个 task 所在的 stage 就失败了。
  stage 失败了则由DAGScheduler 来负责重试,重新发送 TaskSetTaskSchdeulerStage 默认重试 4 次。如果重试 4 次以后依然失败,那么这个 job 就失败了。job 失败了,Application 就失败了。
  TaskScheduler 不仅能重试失败的 task,还会重试 straggling(落后,缓慢)task(也就是执行速度比其他 task 慢太多的 task)。如果有运行缓慢的task ,那么 TaskScheduler 会启动一个新的 task 来与这个运行缓慢的 task执行相同的处理逻辑。两个 task 哪个先执行完,就以哪个 task 的执行结果为准。这就是 Spark 的推测执行机制。在 Spark 中推测执行默认是关闭的。推测执行可以通过 spark.speculation 属性来配置。
注意

  • 对于 ETL 类型(抽取、转换、加载)要入数据库的业务要关闭推测执行机制,这样就不会有重复的数据入库。
  • 如果遇到数据倾斜的情况,开启推测执行则有可能导致一直会有 task重新启动处理相同的逻辑,任务可能一直处于处理不完的状态。
  • 推测执行机制: 考虑到的是资源问题(负载过高),服务器网络波动这样的问题,所导致的计算任务阻塞。

  下面展示standalone-client模式下的资源调度与任务调度:
Scala | 宽窄依赖 | 资源调度与任务调度 | 共享变量 | SparkShuffle | 内存管理_第7张图片

2.资源调度分析

  资源请求简单图:
Scala | 宽窄依赖 | 资源调度与任务调度 | 共享变量 | SparkShuffle | 内存管理_第8张图片
  资源调度 Master 路径:

路径:spark-2.2.1/core/src/main/scala/org.apache.spark/deploy/master/Master.scala

  提交应用程序,submit 的路径:

路径:spark-2.2.1/core/src/main/scala/org.apache.spark/deploy/SparkSubmit.scala

总结

  1. Executor 在集群中分散启动,有利于 task 计算的数据本地化。
  2. 默认情况下(提交任务的时候没有设置--executor-cores 选项),每一个 Worker 为当前的Application 启动一个 Executor,这个Executor 会使用这个 Worker 的所有的 cores1G 内存。
  3. 如果想在 Worker 上启动多个 Executor,提交 Application 的时候要加--executor-cores 这个选项。
  4. 默认情况下没有设置--total-executor-cores,一个 Application 会使用 Spark 集群中所有的 cores

结论演示

  • 默认情况每个 worker 为当前的 Application 启动一个 Executor,这 个 Executor 使用集群中所有的 cores1G 内存。

    ./spark-submit
    --master spark://node1:7077
    --class org.apache.spark.examples.SparkPi
    ../examples/jars/spark-examples_2.11-2.2.1.jar
    10000
    
  • workr 上启动多个 Executor,设置--executor-cores 参数指定每个executor 使用的 core 数量。

    ./spark-submit
    --master spark://node1:7077
    --executor-cores 1
    --class org.apache.spark.examples.SparkPi
    ../examples/jars/spark-examples_2.11-2.2.1.jar
    10000
    
  • 内存不足的情况下启动 core 的情况。Spark 启动是不仅看 core 配置参数,也要看配置的 core 的内存是否够用。

    ./spark-submit
    --master spark://node1:7077
    --executor-cores 1
    --executor-memory 3g
    --class org.apache.spark.examples.SparkPi
    ../examples/jars/spark-examples_2.11-2.2.1.jar
    10000
    
  • --total-executor-cores 集群中共使用多少 cores
    注意:一个进程不能让集群多个节点共同启动。

    ./spark-submit
    --master spark://node1:7077
    --executor-cores 1
    --executor-memory 2g
    --total-executor-cores 3
    --class org.apache.spark.examples.SparkPi
    ../examples/jars/spark-examples_2.11-2.2.1.jar
    10000
    

3.任务调度分析

  Action 算子开始分析,任务调度可以从一个 Action 类算子开始。因为 Action 类算子会触发一个 job 的执行。
  划分 stage,以 taskSet 形式提交任务。DAGScheduler 类中 getMessingParentStages()方法是切割 job 划分stage 。 可以结合以下这张图来分析:
Scala | 宽窄依赖 | 资源调度与任务调度 | 共享变量 | SparkShuffle | 内存管理_第9张图片

4.粗粒度资源申请和细粒度资源申请

4.1 粗粒度资源申请(Spark)

  在 Application 执行之前,将所有的资源申请完毕,当资源申请成功后,才会进行任务的调度,当所有的 task 执行完成后,才会释放这部分资源。

  • 优点:在 Application 执行之前,所有的资源都申请完毕,每一个task 直接使用资源就可以了,不需要 task 在执行前自己去申请资源,task 启动就快了,task 执行快了,stage 执行就快了,job 就快了,application 执行就快了。
  • 缺点:直到最后一个 task 执行完成才会释放资源,集群的资源无法充分利用。

4.2 细粒度资源申请(MR)

  Application 执行之前不需要先去申请资源,而是直接执行,让 job中的每一个 task 在执行前自己去申请资源,task 执行完成就释放资源。

  • 优点:集群的资源可以充分利用。
  • 缺点:task 自己去申请资源,task 启动变慢,Application的运行就响应的变慢了。

因为MR本身是细粒度的,所以优化的最简单方式就是改成粗粒度
mapred.job.reuse.jvm.num.tasks=n;设置这个ntask插槽的个数。

五、共享变量

1.广播变量

  广播变量理解图如下。在没有使用广播变量时,list需要发送到多个exexutor上,此时,有可能多个executor在同一台机器上。使用广播变量后,只需要将变量缓存到每一台机器上即可。
Scala | 宽窄依赖 | 资源调度与任务调度 | 共享变量 | SparkShuffle | 内存管理_第10张图片
  广播变量使用如下:

package com.shsxt.scala_Test.core.test

import org.apache.spark.broadcast.Broadcast
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

object BroadCastTest {


  def main(args: Array[String]): Unit = {

    val conf: SparkConf = new SparkConf()
    conf.setMaster("local").setAppName("brocast")
    val sc: SparkContext = new SparkContext(conf)
    val list: List[String] = List("hello Spark")
    //显示创建
    val broadCast: Broadcast[List[String]] = sc.broadcast(list)
    val lineRDD: RDD[String] = sc.textFile("data/word.txt")

    lineRDD.filter(x => {
      //广播变量值获取
      val list: List[String] = broadCast.value
      list.contains(x)
    }).foreach {
      println
    }
    sc.stop()
  }
}
hello Spark

注意事项

  • 广播变量只能在 Driver 端定义,不能在 Executor 端定义。
  • 广播变量值是只读的。在 Driver 端可以修改广播变量的值,在 Executor 端无法修改广播变量的值。

2.累加器

  累加器理解图如下:
Scala | 宽窄依赖 | 资源调度与任务调度 | 共享变量 | SparkShuffle | 内存管理_第11张图片
  累加器使用如下:

package com.shsxt.scala_Test.core.test

import org.apache.spark.util.LongAccumulator
import org.apache.spark.{SparkConf, SparkContext}

object AccumulatorTest {

  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf()
    conf.setMaster("local").setAppName("accumulator")

    val sc: SparkContext = new SparkContext(conf)

    val accumulator: LongAccumulator = sc.longAccumulator
    //
    sc.textFile("data/word.txt", 2).foreach { x => {
      accumulator.add(1)
    }
    }

    println("accumulator.value : " + accumulator.value)

    sc.stop()
  }

}
accumulator.value : 4

注意事项:

  • 累加器在 Driver 端定义赋初始值,累加器只能在 Driver 端读取, 在 Excutor 端更新。

六、SparkShuffle

1.SparkShuffle 概念

  reduceByKey 会将上一个 RDD 中的每一个 key 对应的所有 value 聚合成一个 value,然后生成一个新的 RDD,元素类型是对的形式,这样每一个 key 对应一个聚合起来的 value

  • 问题:聚合之前,每一个 key 对应的 value 不一定都是在一个 partition中,也不太可能在同一个节点上,因为 RDD 是分布式的弹性的数据集,RDDpartition 极有可能分布在各个节点上。如何聚合
    • Shuffle Write:上一个 stage 的每个 map task 就必须保证将自己处理的当前分区的数据相同的 key 写入一个分区文件中,可能会写入多个不同的分区文件中。
    • Shuffle Readreduce task 就会从上一个 stage 的所有 task 所在的机器上寻找属于己的那些分区文件,这样就可以保证每一个 key 所对应的 value 都会汇聚到同一个节点上去处理和聚合。

  Spark 中有两种 Shuffle 类型,HashShuffleSortShuffleSpark1.2之前是 HashShuffleSpark1.2之后引入 SortShuffle

2.HashShuffle

  普通机制示意图如下:
Scala | 宽窄依赖 | 资源调度与任务调度 | 共享变量 | SparkShuffle | 内存管理_第12张图片
执行流程:

  • 每一个 map task 将不同结果写到不同的 buffer 中,每个buffer 的大小为 32K。buffer 起到数据缓存的作用。
  • 每个 buffer 文件最后对应一个磁盘小文件。
  • reduce task 来拉取对应的磁盘小文件。

总结:

  • .map task 的计算结果会根据分区器(默认是hashPartitioner)来决定写入到哪一个磁盘小文件中去。
    ReduceTask 会去 Map 端拉取相应的磁盘小文件。
  • 产生的磁盘小文件的个数:M(map task 的个数)*R(reduce task 的个数)

存在的问题:产生的磁盘小文件过多,会导致以下问题:

  • Shuffle Write 过程中会产生很多写磁盘小文件的对象。
  • Shuffle Read 过程中会产生很多读取磁盘小文件的对象。
  • JVM堆内存中对象过多会造成频繁的gcgc还无法解决运行所需要的内存的话,就会 OOM
  • 在数据传输过程中会有频繁的网络通信,频繁的网络通信出现通信故障的可能性大大增加,一旦网络通信出现了故障会导致 shuffle file cannot find 。由于这个错误导致的 task 失 败,TaskScheduler 不负责重试,由 DAGScheduler 负责重试 Stage。

  合并机制示意图如下:
Scala | 宽窄依赖 | 资源调度与任务调度 | 共享变量 | SparkShuffle | 内存管理_第13张图片
总结:产生磁盘小文件的个数:C(core 的个数)*R(reduce 的个数)

3.SortShuffle

  普通机制示意图:
Scala | 宽窄依赖 | 资源调度与任务调度 | 共享变量 | SparkShuffle | 内存管理_第14张图片
执行流程

  • map task 的计算结果会写入到一个内存数据结构里面,内存数据结构默认是 5M
  • shuffle 的时候会有一个定时器,不定期的去估算这个内存结构的大小,当内存结构中的数据超过 5M 时,比如现在内存结构中的数据为 5.01M,那么他会申请 5.01*2-5=5.02M 内存给内存数据结构。
  • 如果申请成功不会进行溢写,如果申请不成功,这时候会发生溢写磁盘。
  • 在溢写之前内存结构中的数据会进行排序分区。
  • 然后开始溢写磁盘,写磁盘是以 batch 的形式去写,一个 batch 是 1 万条数据,
  • map task 执行完成后,会将这些磁盘小文件合并成一个大的磁盘文件,同时生成一个索引文件。
  • reduce taskmap 端拉取数据的时候,首先解析索引文件,根据索引文件再去拉取对应的数据。

总结

  • 产生磁盘小文件的个数: 2*M(map task 的个数)

  bypass 机制示意图:
Scala | 宽窄依赖 | 资源调度与任务调度 | 共享变量 | SparkShuffle | 内存管理_第15张图片
总结

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

    • shuffle reduce task 的数量小于spark.shuffle.sort.bypassMergeThreshold 的参数值。这个值默认是 200。
    • 不需要进行 map 端的预聚合,比如:groupBykeyjoin
  • 产生的磁盘小文件为:2*M(map task 的个数)

4.Shuffle文件寻址

  1. MapOutputTracker
    MapOutputTrackerSpark 架构中的一个模块,是一个主从架构。管理磁盘小文件的地址。

    • MapOutputTrackerMaster 是主对象,存在于 Driver 中。
    • MapOutputTrackerWorker 是从对象,存在于 Excutor 中。
  2. BlockManager
    BlockManager 块管理者,是 Spark 架构中的一个模块,也是一个主从架构。

    • BlockManagerMaster,主对象,存在于 Driver 中。
      BlockManagerMaster 会在集群中有用到广播变量和缓存数据或者删除缓存数据的时候,通知 BlockManagerSlave 传输或者删除数据。
    • BlockManagerWorker,从对象,存在于 Excutor 中。
      BlockManagerWorker 会与 BlockManagerWorker 之间通信。

    无论在 Driver 端的 BlockManager 还是在 Excutor 端的BlockManager 都含有四个对象:

    • DiskStore:负责磁盘的管理。
    • MemoryStore:负责内存的管理。
    • ConnectionManager:负责连接其他的BlockManagerWorker
    • BlockTransferService:负责数据的传输。
  3. Shuffle 文件寻址图
    Scala | 宽窄依赖 | 资源调度与任务调度 | 共享变量 | SparkShuffle | 内存管理_第16张图片

  4. Shuffle 文件寻址流程

    • map task 执行完成后,会将 task 的执行情况和磁盘小文件的地址封装到 MpStatus 对象中,通过MapOutputTrackerWorker 对象向 Driver 中的MapOutputTrackerMaster 汇报。
    • 在所有的 map task 执行完毕后,Driver 中就掌握了所有的磁盘小文件的地址。
    • reduce task 执行之前,会通过ExcutorMapOutPutTrackerWorkerDriver 端的MapOutputTrackerMaster 获取磁盘小文件的地址。
    • 获取到磁盘小文件的地址后,会通过 BlockManager 中的ConnectionManager 连接数据所在节点上的ConnectionManager,然后通过 BlockTransferService 进行数据的传输。
    • BlockTransferService 默认启动 5 个 task 去节点拉取数据。默认情况下,5 个 task 拉取数据量不能超过 48M。

5.Shuffle 参数调优

  SparkShuffle 调优配置项如何使用?

  • 在代码中,不推荐使用,硬编码。
    new SparkConf().set(“spark.shuffle.file.buffer”,64)
    
  • 在提交 spark 任务的时候,推荐使用
    spark-submit --conf spark.shuffle.file.buffer=64 –conf ….
    
  • 在 conf 下的 spark-default.conf 配置文件中,不推荐,因为是写死后所有应用程序都要用。

  Shuffle 参数调优,可调整参数介绍:
spark.shuffle.file.buffer

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

spark.reducer.maxSizelnFlight

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

spark.shuffle.io.maxRetries

  • 默认值:3
  • 参数说明: shuffle read task从shuffle write task所在节点拉取属于自己的数据时,如果因为网络异常导致拉取失败,是会自动进行重试的。该参数就代表了可以重试的最大次数。如果在指定次数之内拉取还是没有成功,就可能会导致作业执行失败。
  • 调优建议:对于那些包含了特别耗时的shuffle操作的作业,建议增加重试最大次数(比如60次),以避免由于JVM的full gc或者网络不稳定等因素导致的数据拉取失败。在实践中发现,对于针对超大数据量(数十亿-上百亿)的shuffle过程,调节该参数可以大幅度提升稳定性。
    shuffle file not find taskScheduler不负责重试task,由DAGScheduler负责重试stage。

spark.shuffle.io.retryWait

  • 默认值:5s
  • 参数说明:具体解释同上,该参数代表了每次重试拉取数据的等待间隔,默认是5s。
  • 调优建议:建议加大间隔时长(比如60s),以增加shuffle操作的稳定性。

spark.shuffle.memoryFraction

  • 默认值:0.2
  • 参数说明:该参数代表了Executor内存中,分配给shuffle read task进行聚合操作的内存比例,默认是20%。
  • 调优建议:在资源参数调优中讲解过这个参数。如果内存充足,而且很少使用持久化操作,建议调高这个比例,给shuffle read的聚合操作更多内存,以避免由于内存不足导致聚合过程中频繁读写磁盘。在实践中发现,合理调节该参数可以将性能提升10%左右。

spark.shuffle.manager

  • 默认值:sort|hash
  • 参数说明:该参数用于设置ShuffleManager的类型。Spark 1.5以后,有三个可选项: hash、sort和tungsten-sort,HashShuffleManager是Spark 1.2以前的默认选项,但是Spark 1.2以及之后的版本默认都是SortShuffleManager了。tungsten-sort与sort类似,但是使用了tungsten计划中的堆外内存管理机制,内存使用效率更高。
  • 调优建议:由于SortShuffleManager默认会对数据进行排序,因此如果你的业务逻辑中需要该排序机制的话,则使用默认的SortShuffleManager就可以;而如果你的业务逻辑不需要对数据进行排序,那么建议参考后面的几个参数调优,通过bypass机制或优化的HashShuffleManager来避免排序操作,同时提供较好的磁盘读写性能。这里要注意的是,tungsten-sort要慎用,因为之前发现了一些相应的bug。

spark.shuffle.sort.bypassMergeThreshold

  • 默认值:200
  • 参数说明:当ShuffleManager为SortShuffleManager时,如果shuffle read task的数显小于这个阈值(默认是200),则shuffle write过程中不会进行排序操作,而是直接按照未经优化的HashShuffleManager的方式去写数据,但是最后会将每个task产生的所有临时磁盘文件都合并成一个文件,并会创建单独的索引文件。
  • 调优建议:当你使用SortShuffleManager时,如果的确不需要排序操作,那么建议将这个参数调大一些,大于shuffle read task的数量。那么此时就会自动启用bypass机制,map-side就不会进行排序了,减种了排序的性能开销。但是这种方式下,依然会产生大量的磁盘文件,因此shuffle write性能有待提高。

spark.shuffle.consolidateFiles

  • 默认值:false
  • 参数说明:如果使用HashShuffleManager,该参数有效。如果设置为true,那么就会开启consolidate机制,会大幅度合并shuffle write的输出文件,对于shuffle read task数量特别多的情况下,这种方法可以极大地减少磁盘IO开销,提升性能。
  • 调优建议:如果的确不需要SortShuffleManager的排序机制,那么除了使用bypass机制,还可以尝试将spark.shffle.manager参数手动指定为hash,使用HashShuffleManager,同时开启consolidate机制。在实践中尝试过,发现其性能比开启了bypass机制的SortShuffleManager要高出10%~30%。

七、Spark内存管理

  Spark 执行应用程序时,Spark 集群会启动 DriverExecutor 两种 JVM 进程,Driver 负责创建 SparkContext 上下文,提交任务,task 的分发等。Executor 负责 task 的计算任务,并将结果返回给 Driver。同时需要为需要持久化的 RDD 提供储存。
  Driver 端的内存管理比较简单,这里所说的 Spark内存管理针对 Executor 端的内存管理。Spark 内存管理分为静态内存管理统一内存管理,Spark1.6 之前使用的是静态内存管理,Spark1.6 之后引入了统一内存管理。
  静态内存管理中存储内存、执行内存和其他内存的大小在 Spark 应用程序运行期间均为固定的,但用户可以应用程序启动前进行配置。
  统一内存管理与静态内存管理的区别在于储存内存和执行内存共享同一块空间,可以互相借用对方的空间
  Spar1.6 及 1.6 版本之后的版本默认使用的是统一内存管理。要想使用静态内存可以通过参数 spark.memory.useLegacyMode,设置为 true(默认为 false)使用静态内存管理。

  1. 静态内存管理分布图
    内存管理

Scala | 宽窄依赖 | 资源调度与任务调度 | 共享变量 | SparkShuffle | 内存管理_第17张图片
2. 统一内存管理分布图
内存管理
Scala | 宽窄依赖 | 资源调度与任务调度 | 共享变量 | SparkShuffle | 内存管理_第18张图片
3. reduceOOM 如何处理?
- 减少每次拉取的数据量
- 提高 shuffle 聚合的内存比例
- 提高 Excutor 的总内存

你可能感兴趣的:(Spark,spark,宽窄依赖,共享变量,Shuffle,资源调度与任务调度)