编码优化:


① RDD 复用


② RDD 持久化


③ 巧用 filter


④ 选择高性能算子


⑤ 设置合并的并行度


⑥ 广播大变量


⑦ Kryo 序列化


⑧ 多使用 Spark SQL


⑨ 优化数据结构


⑩ 使用高性能库


 


参数优化:



① Shuffle 调优



② 内存调优



③ 资源分配



④ 动态资源分配



⑤ 调节本地等待时长



⑥ 调节连接等待时长

 


1 编码的优化

1、RDD复用



避免创建重复的 RDD 。在开发过程中要注意:对于同一份数据,只应该创建一个RDD,不要创建多个 RDD 来代表同一份数据。



 



2、RDD缓存/持久化

  • 当多次对同一个RDD执行算子操作时,每一次都会对这个RDD以之前的父RDD重新计算一次,这种情况是必须要避免的,对同一个RDD的重复计算是对资源的极大浪费
  • 对多次使用的RDD进行持久化,通过持久化将公共RDD的数据缓存到内存/磁盘中,之后对于公共RDD的计算都会从内存/磁盘中直接获取RDD数据
  • RDD的持久化是可以进行序列化的,当内存无法将RDD的数据完整的进行存放的时候,可以考虑使用序列化的方式减小数据体积,将数据完整存储在内存中

3、巧用 filter

  • 尽可能早的执行filter操作,过滤无用数据
  • 在filter过滤掉较多数据后,使用 coalesce 对数据进行重分区

4、使用高性能算子



1 、避免使用 groupByKey ,根据场景选择使用高性能的聚合算子 reduceByKey 、 aggregateByKey



2、coalesce、repartition,选择没有shuffle的操作
3、foreachPartition 优化输出操作
4、map、mapPartitions,选择合理的选择算子 mapPartitions性能更好,但数据量大时容易导致OOM
5、用 repartitionAndSortWithinPartitions 替代 repartition + sort 操作
6、合理使用 cache、persist、checkpoint,选择合理的数据存储级别
7、filter的使用

5、设置合理的并行度

  • Spark作业中的并行度指各个stage的task的数量
  • 设置合理的并行度,让并行度与资源相匹配。简单来说就是在资源允许的前提下,并行度要设置的尽可能大,达到可以充分利用集群资源。合理的设置并行 度,可以提升整个Spark作业的性能和运行速度

6、广播大变量

  • 默认情况下,task中的算子中如果使用了外部变量,每个task都会获取一份变量的复本,这会造多余的网络传输和内存消耗
  • 使用广播变量,只会在每个Executor保存一个副本,Executor的所有task共用此广播变量,这样就节约了网络及内存资源

7、Kryo序列化

  • 默认情况下,Spark使用Java的序列化机制。Java的序列化机制使用方便,不需要额外的配置。但Java序列化机制效率不高,序列化速度慢而且序列化后的数据占用的空间大
  • Kryo序列化机制比Java序列化机制性能提高10倍左右。Spark之所以没有默认使用Kryo作为序列化类库,是它不支持所有对象的序列化,同时Kryo需要用户在使用前注册需要序列化的类型,不够方便。从Spark 2.0开始,简单类型、简单类型数组、字符串类型的 Shuffling RDDs 已经默认使用 Kryo 序列化方式

8、多使用Spark SQL

  • Spark SQL 编码更容易,开发更简单
  • Spark的优化器对SQL语句做了大量的优化,一般情况下实现同样的功能,Spark SQL更容易也更高效

9、优化数据结构



Spark 中有三种类型比较消耗内存:



  • 对象。每个Java对象都有对象头、引用等额外的信息,占用了额外的内存空间
  • 字符串。每个字符串内部都有一个字符数组以及长度等额外信息
  • 集合类型。如HashMap、LinkedList等,集合类型内部通常会使用一些内部类来封装集合元素

10、使用高性能库

  • fastutil是扩展了Java标准集合框架 (Map、List、Set;HashMap、ArrayList、HashSet) 的类库,提供了特殊类型的map、set、list和queue
  • fastutil能够提供更小的内存占用、更快的存取速度;可使用fastutil提供的集合类,来替代JDK原生的Map、List、Set。好处在于使用fastutil集合类,可以减小内存占用,在进行集合操作时,提供更快的存取速度

2 参数优化

1、Shuffle 调优



 



2、内存调优



 



3、资源优化



 



4、动态资源分配



动态资源分配 (DRA ,dynamic resource allocation)

  • 默认情况下,Spark采用资源预分配的方式。即为每个Spark应用设定一个最大可用资源总量,该应用在整个生命周期内都会持有这些资源
  • Spark提供了一种机制,使它可以根据工作负载动态调整应用程序占用的资源。这意味着,不使用的资源,应用程序会将资源返回给集群,并在稍后需要时再次请求资源。如果多个应用程序共享Spark集群中的资源,该特性尤为有用



  • 默认情况下禁用此功能,并在所有粗粒度集群管理器上可用(CDH发行版中默认为true)
  • 动态的资源分配是 executor 级
  • 在Spark On Yarn模式下使用: num-executors 指定 app 使用 executors 数量
    executor-memory 、 executor-cores 指定每个 executor 所使用的内存、 cores

 



动态申请 executor



 



如果有新任务处于等待状态,并且等待时间超过 Spark.dynamicAllocation.schedulerBacklogTimeout(默认 1s),则会依次启动executor,每次启动 1 、 2 、 4 、 8… 个 executor(如果有的话)。 启动的间隔由 spark.dynamicAllocation.sustainedSchedulerBacklogTimeout 控制 ( 默认与 schedulerBacklogTimeout 相同 )



 



动态移除 executor

executor 空闲时间超过 spark.dynamicAllocation.executorIdleTimeout 设置的 值( 默认 60s) ,该 executor 会被移除,除非有缓存数据



 



相关参数:



spark.dynamicAllocation.enabled = true



Yarn 模式:《 Running Spark on YARN 》 -- Configuring the External Shuffle Service



external shuffle service 的目的是在移除 Executor的时候,能够保留 Executor 输出的 shuffle 文件



  • spark.dynamicAllocation.executorIdleTimeout(默认60s)。Executor闲置了超过此持续时间,将被删除
  • spark.dynamicAllocation.cachedExecutorIdleTimeout(默认infinity)。已缓存数据块的 Executor 闲置了超过此持续时间,则该执行器将被删除
  • spark.dynamicAllocation.initialExecutors(默认spark.dynamicAllocation.minExecutors)。初始分配 Executor 的个数。如果设置了--num-executors(或spark.executor.instances)并且大于此值,该参数将作为 Executor 初始的个数
  • spark.dynamicAllocation.maxExecutors(默认infinity)。Executor 数量的上限
  • spark.dynamicAllocation.minExecutors(默认0)。Executor 数量的下限
  • spark.dynamicAllocation.schedulerBacklogTimeout(默认1s)。任务等待时间超过了此期限,则将请求新的Executor



5、调节本地等待时长

  • Spark总是倾向于让所有任务都具有最佳的数据本地性。遵循移动计算不移动数据的思想,Spark希望task能够运行在它要计算的数据所在的节点上,这样可以避免数据的网络传输 PROCESS_LOCAL > NODE_LOCAL > NO_PREF > RACK_LOCAL > ANY
  • 在某些情况下,可能会出现一些空闲的executor没有待处理的数据,那么Spark可能就会牺牲一些数据本地
  • 如果对应节点资源用尽,Spark会等待一段时间(默认3s)。如果等待指定时间后仍无法在该节点运行,那么自动降级,尝试将task分配到比较差的本地化级别所对应的节点上;如果当前级别仍然不行,那么继续降级
  • 调节本地等待时长。如果在等待时间内,目标节点处理完成了一部分 Task ,那么等待运行的 Task 将有机会得到执行,获得较好的数据本地性,提高 Spark 作业整体性能
  • 根据数据本地性不同,等待的时间间隔也不一致,不同数据本地性的等待时间设置参数 spark.locality.wait :设置所有级别的数据本地性,默认是 3000毫秒 spark.locality.wait.process :多长时间等不到 PROCESS_LOCAL就降级,默认为${spark.locality.wait}
    spark.locality.wait.node :多长时间等不到 NODE_LOCAL就降级,默认为${spark.locality.wait}
    spark.locality.wait.rack :多长时间等不到 RACK_LOCAL 就降级,默认为 ${spark.locality.wait}



6、调节连接等待时长



在 Spark 作业运行过程中, Executor 优先从自己本地关联的 BlockManager 中获取某份数据,如果本地BlockManager 没有的话,会通过 TransferService 远程连接其他节点上Executor 的 BlockManager来获取数据。

在生产环境下,有时会遇到 file not found 、 file lost 这类错误。这些错误很有可能是Executor的 BlockManager 在拉取数据的时候,无法建立连接,然后超过默认的连接等待时长后,宣告数据拉取失败。如果反复尝试都拉取不到数据,可能会导致Spark作业的崩溃。这种情况也可能会导致 DAGScheduler 反复提交几次 stage,TaskScheduler反复提交几次task ,延长了 Spark 作业的运行时间;



此时,可以考虑调节连接的超时时长,设置: spark.core.connection.ack.wait.timeout = 300s (缺省值120s) 调节连接等待时长后,通常可以避免部分的文件拉取失败、文件丢失等报错