常见的Spark的调优方法及数据倾斜的处理

Spark调优

一、 常规调优

常规性能调优一:最优资源配置

Spark性能调优的第一步,就是为任务分配更多的资源,在一定范围内,增加资源的分配与性能的提升是成正比的,实现了最优的资源配置

常见的Spark的调优方法及数据倾斜的处理_第1张图片
开启内存联合机制,execution与storage两者可以相互借用内存

常规性能调优二:RDD优化

RDD复用:对RDD进行算子时,要避免相同的算子和计算逻辑之下对RDD进行重复的计算

RDD持久化:在Spark中,当多次对同一个RDD执行算子操作时,每一次都会对这个RDD以之前的父RDD重新计算一次,这种情况是必须要避免的,对同一个RDD的重复计算是对资源的极大浪费,因此,必须对多次使用的RDD进行持久化,通过持久化将公共RDD的数据缓存到内存/磁盘中,之后对于公共RDD的计算都会从内存/磁盘中直接获取RDD数据。

常规性能调优三:并行度调节

Spark作业中的并行度指各个stage的task的数量。

如果并行度设置不合理而导致并行度过低,会导致资源的极大浪费

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

例如,20个Executor,每个Executor分配3个CPU core,而Spark作业有40个task,这样每个Executor分配到的task个数是2个,这就使得每个Executor有一个CPU core空闲,导致资源的浪费。

常规性能调优四:广播大变量

Broadcast广播变量

广播变量是用来高效分发较大的数据,向所有的worker发送一个较大的只读值,以提供一个或多个spark进行操作
常见的Spark的调优方法及数据倾斜的处理_第2张图片

广播变量的过程如下

  1. 通过对一个类型 T 的对象调用 SparkContext.broadcast 创建出一个 Broadcast[T] 对象。 任何可序列化的类型都可以这么实现。
  2. 通过 value 属性访问该对象的值(在 Java 中为 value() 方法)。
  3. 变量只会被发到各个节点一次,应作为只读值处理(修改这个值不会影响到别的节点)。 能不能将一个RDD使用广播变量广播出去? 不能,因为RDD是不存储数据的。可以将RDD的结果广播出去。 广播变量只能在Driver端定义,不能在Executor端定义。

默认情况下,task中的算子中如果使用了外部的变量,每个task都会获取一份变量的复本,这就造成了内存的极大消耗。

一方面,如果后续对RDD进行持久化,可能就无法将RDD数据存入内存,只能写入磁盘,磁盘IO将会严重消耗性能;

另一方面,task在创建对象的时候,也许会发现堆内存无法存放新创建的对象,这就会导致频繁的GC,GC会导致工作线程停止,进而导致Spark暂停工作一段时间,严重影响Spark性能。

如果使用了广播变量, 那么每个Executor保存一个副本,一共消耗400M内存,减少了内存消耗

常规性能调优五:Kryo序列化

默认情况下,Spark使用Java的序列化机制。

Java的序列化机制使用方便,但是Java序列化机制的效率不高,序列化速度慢并且序列化后的数据所占用的空间依然较大。

Kryo序列化机制比Java序列化机制性能提高10倍左右

从Spark 2.0.0版本开始,简单类型、简单类型数组、字符串类型的Shuffling RDDs 已经默认使用Kryo序列化方式了。

常规性能调优六:调节本地化等待时长

通常来说,task可能不会被分配到它处理的数据所在的节点,因为这些节点可用的资源可能已经用尽。数据本地化,这样就可以避免数据的网络传输。此时,Spark会等待一段时间,默认3s,如果等待指定时间后仍然无法在指定节点运行,那么会自动降级。将task分配到离它要计算的数据比较近的一个节点,然后进行计算

Spark节点尽量覆盖HBase,因为spark读取Hbase时是按照region读取,避免大量数据迁移

二、算子调优

使用Map Partition代替map

如果是普通的map算子,假设一个partition有1万条数据,那么map算子中的function要执行1万次,也就是对每个元素进行操作。

如果是mapPartition算子,由于一个task处理一个RDD的partition,那么一个task只会执行一次 function,function一次接收所有的partition数据,效率比较高。可以有效的提高效率,但是内存紧缺时不要使用。

使用CombineByKey算子

进一步减少网络IO

使用foreachPartition算子

数据库写入,通过foreachPartition算子的特性,可以优化写数据库的性能。如果使用foreach算子完成数据库的操作,由于foreach算子是遍历RDD的每条数据,因此,每条数据都会建立一个数据库连接,这是对资源的极大浪费

filter与coalesce的配合使用

分区中加载数据量相近,一旦经过filter过滤后,每个分区的数据量有可能会存在较大差异造成数据倾斜,此时使用coalesce算子进行重新分区,可以避免集群资源浪费和数据倾斜问题

repartition解决SparkSQL低并行度问题

使用reduceByKey代替其他的shuffle算子

使用reduceByKey对性能的提升如下:

  1. 本地聚合后,在map端的数据量变少,减少了磁盘IO,也减少了对磁盘空间的占用;
  2. 本地聚合后,下一个stage拉取的数据量变少,减少了网络传输的数据量;
  3. 本地聚合后,在reduce端进行数据缓存的内存占用减少;
  4. 本地聚合后,在reduce端进行聚合的数据量减少

三、Shuffle调优

调节map端缓冲区大小

通过调节map端缓冲的大小,可以避免频繁的磁盘IO操作,进而提升Spark任务的整体性能。

调节reduce端拉取数据缓冲区大小

适当增加拉取数据缓冲区的大小,可以减少拉取数据的次数,也就可以减少网络传输的次数,进而提升性能。

调节reduce端拉取数据重试次数

reduce task拉取属于自己的数据时,如果因为网络异常等原因导致失败会自动进行重试。对于那些包含了特别耗时的shuffle操作的作业,增加重试最大次数,避免由于JVM的full gc或者网络不稳定等因素导致的数据拉取失败

调节reduce端拉取数据等待间隔

reduce task拉取属于自己的数据时,如果因为网络异常等原因导致失败会自动进行重试,在一次失败后,会等待一定的时间间隔再进行重试,可以通过加大间隔时长,以增加shuffle操作的稳定性。

调节SortShuffle排序操作阈值

如果不需要排序操作,可以将bypass参数调大,大于shuffle read task的数量,那么不会 进行排序了,减少了排序的性能开销。

会触发shuffle操作的算子

  • 去重 distinct
  • 聚合 reduceBykey aggregateBykey combineByKey
  • 分组 groupBy groupByKey
  • 排序 sortByKey sortBy
  • 重新分区 repartition coalesce

四、JVM调优

JVM调优一:降低cache操作的内存占比

开启统一内存管理机制
shuffle过程需要的内存过大时,会自动占用Storage的内存区域,因此无需手动进行调节。

JVM调优二:调节Executor堆外内存

堆外内存调节的比较大的时候,对于性能来讲会带来一定的提升

JVM调优三:调节连接等待时长

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

如果task在运行过程中创建的对象较大,会导致频繁GC,垃圾回收一旦执行,Spark的Executor进程就会停止工作,由于没有响应,无法建立网络连接,会导致网络连接超时。

此时,可以考虑调节连接的超时时长

数据倾斜处理

数据倾斜的表现

  1. Spark作业的大部分task都执行迅速,只有有限的几个task执行的非常慢,此时可能出现了数据倾斜,作业可以运行,但是运行得非常慢;
  2. Spark作业的大部分task都执行迅速,但是有的task在运行过程中会突然报出OOM,反复执行几次都在某一个task报出OOM错误,此时可能出现了数据倾斜,作业无法正常运行。

定位数据倾斜问题:

  1. 查阅代码中的shuffle算子,例如reduceByKey、countByKey、groupByKey、join等算子,根据代码逻辑判断此处是否会出现数据倾斜;
  2. 查看Spark作业的log文件,log文件对于错误的记录会精确到代码的某一行,可以根据异常定位到
    的代码位置来明确错误发生在第几个stage,对应的shuffle算子是哪一个;
    常见的Spark的调优方法及数据倾斜的处理_第3张图片

附加知识

累加器

作用:

  1. 可以作为调试工具,能够观察每个task的信息通过累加器在sparkUI上观察task所处理的记录
  2. 能够精准的统计数据的各种值:
    2.1 ETL数据清洗,使用累加器进行数据统计(清洗字段的总数)
    2.2 可以同时浏览userID的记录,在同一个时间段内产生多少次购买

Accumulator累加器
自定义累加器(自定义累加器 需要继承AccumulatorV2类)

创建累加器: (无论是自定义的还是系统提供的 )

  1. 创建当前累加器对象
  2. 通过SparkContext对象的register(累加器对象,累加器名称[字符串类型])方法来注册一个累加器
  3. 通过累加器对象.add方法来进行进行数据计算
  4. 获取累加器的计算结果,通过value方法来获取

相关文章:
Spark的运行模式及 Standalone模式的运行流程
Spark 弹性分布数据集RDD的介绍
Spark的内存模型以及Spark Shuffle的原理

有收获?希望烙铁们来个三连击,让更多的同学看到这篇文章

1、烙铁们,关注我看完保证有所收获,不信你打我。

2、点个赞呗,可以让更多的人看到这篇文章,后续还会有很哇塞的产出。

本文章仅供学习及个人复习使用,如需转载请标明转载出处,如有错漏欢迎指出
务必注明来源(注明: 来源:csdn , 作者:-马什么梅-)

你可能感兴趣的:(spark,大数据)