spark笔记-spark性能优化

1. spark基本概念

spark

  • 每一台host上面可以并行N个worker,每一个worker下面可以并行M个executor, task们会被分配到executor上面去执行。Stage指的是一组并行运行的task,stage内部是不能出现shuffle的。
  • CPU的core数量,每个executor可以占用一个或多个core,可以通过观察CPU的使用率变化来了解计算资源的使用情况。
  • partition和parallelism,partition指的就是数据分片的数量,每次一task只能处理一个partition的数据。parallelism则指在RDD进行reduce类操作的时候,默认返回数据的paritition数量

需要注意的是,有的配置在不同的MR框架/工具下是不一样的,比如Yarn下有的参数的默认取值就不同

如果想当Spark应用退出后,仍可以获得历史Spark应用的stages和tasks执行信息,便于分析程序不明原因挂掉的情况。可以开启History Server。配置方法如下:
(1)

$SPARK_HOME/conf/spark-env.sh
export SPARK_HSTORY_OPTS="-Dspark history.retainedApplications=50
Dspark.history.fs.logDirectory=hdfs://master01:9000/directory"

说明: spark.history.retainedApplica-tions仅显示最近50个应用
spark.history.fs.logDirectory: Spark History Server页面只展示该路径下的信息。
(2)

$SPARK_HOME/conf/spark-defaults.conf
spark.eventLog.enabled true 
spark.eventLog.dir hdfs://hadoop000:8020/directory #应用在运行过程中所
有的信息均记录在该属性指定的路径下
spark. eventLog.compress true

(3) HistoryServer启动

$SPARK_HOMR/bin/start-histrory-server.sh

(4) HistoryServer停止

$SPARK_HOMR/bin/stop-histrory-server.sh

同时Executor的logs也是查看的一个出处:

  • Standalone模式: $SPARK_HOME/logs
  • YARN模式:在yarn-site.xml文件中配置了YARN日志的存放位置:
    yarn.nodemanager.log-dirs,或使用命令获取yarn logs-applicationId.

同时通过配置ganglia,可以分析集群的使用状况和资源瓶颈,但是默认情
况下ganglia 是未被打包的,需要在mvn编译时添加-Pspark- ganglia-lgpl,并修
改配置文件$SPARK_HOME/conf/metrics.properties。

2. 数据倾斜优化

2.1 数据倾斜是什么?

在shuffle过程中分配到下游的Task的数据量不平均,导致了每个Task处理的数据量和数据时间有很大差别,导致整个应用的运行大大加长。

2.2 如何定位数据倾斜?

定位数据倾斜

  1. 是不是有OOM情况出现,一般是少数出现内存溢出的问题。
  2. 是不是应用运行时间差异很大,总体时间很长。
  3. 你需要了解所处理的数据Key的分布情况,如果有些Key具有大量的数据,那么就要小心数据倾斜的问题。
  4. 一般需要通过Spark Web UI和其他一些些监控方式中出现的异常来综合判断。
  5. 看看你的代码里面是否有一些导致Shuffle的算子出现

2.3 数据倾斜的几种典型情况

  1. 数据源中的数据分布不匀,Spark需要频繁交互
  2. 数据集中的不同Key由于分区方式,导致数据倾斜
  3. JOIN操作中,一个数据集中的数据分布不均匀,另一个数据集较小
  4. 聚合操作中,数据集中的数据分布不均匀
  5. JOIN操作中,两个数据集都比较大,其中某一数据集中只有几个Key的数据分布不均匀
  6. JOIN操作中,两个数据集都比较大,其中某一数据集中有很多Key的数据分布不均匀
  7. 数据集中少数几个key数据量很大,不重要,其他数据均均

2.4 缓解数据倾斜-避免数据源的数据倾斜

  • 实现原理:通过在hive etl时对数据进行按照key排序或者聚合(shuffle类算子的操作),以及在进行kafka数据分发时尽量进行平均分配,在spark阶段就不必在做或做的少了,执行速度快了,提供更好的用户体验。美团点评在处理统计任务时,在10分钟处理完,他们把shuffle操作提前到了hive etl阶段,直接使用中间表。

  • 方案优点:实现起来简单便捷,效果还非常好,完全规避掉了数据倾斜,Spark
    作业的性能会大幅度提升。

  • 方案缺点:治标不治本,Hive或者Kafka中还是会发生数据倾斜。

  • 适用情况:在一些Java系统与Spark结合使用的项目中,会出现Java代码频繁调用
    Spark作业的场景,而且对Spark作业的执行性能要求很高,就比较适合使用这种方案。
    将数据倾斜提前到上游的Hive ETL,每天仅执行一次,只有那一次是比较慢的,而之
    后每次Java调用Spark作业时,执行速度会很快,能够提供更好的用户体验。

2.5 缓解数据倾斜-调整并行度

  • 实现原理:增加shuffle read task的数量,可以让原本分配给一个task的多个key
    分配给多个task,从而让每个task处理比原来更少的数据。
  • 方案优点:实现起来比较简单,可以有效缓解和减轻数据倾斜的影响。
  • 方案缺点: 只是缓解了数据倾斜而已,没有彻底根除问题,根据实践经验来看,
    其效果有限。
  • 实践经验:该方案通常无法彻底解决数据倾斜,因为如果出现一些极端情况,比如某个key对应的数据量有100万,那么无论你的task数量增加到多少,都无法处理。
kvrdd.groupByKey(12).count

并行度

2.6 缓解数据倾斜-自定义Partitioner

  • 适用场景:大量不同的Key被分配到了相同的Task造成该Task数据量过大。
  • 解决方案:使用自定义的Partitioner实现类代替默认的HashPartitioner,尽量将所有不同的Key均匀分配到不同的Task中。
  • 优势:不影响原有的并行度设计。如果改变并行度,后续Stage的并行度也会默认改变,可能会影响后续Stage。
  • 劣势:适用场景有限,只能将不同Key分散开,对于同一Key对应数据集非常大的场景不适用。效果与调整并行度类似,只能缓解数据倾斜而不能完全消除数据倾斜。而且需要根据数据特点自定义专用的Partitioner,不够灵活。
class CustomerPartitioner (numParts:Int) extends org.apache.spark.Partitioner {
//覆盖分区数.
override def numPartitions: Int = numParts
//覆盖分区号获取函数.
override def getPartition(key: Any): Int = {
    val id: Int = key.toString.toInt
    if (id <= 900000)
        return new java util. Random(). nextInt(100) % 12.
    else
        return id % 12。
    }
}

kvRdd.groupByKey(new CustomerPartitioner(12)).count

2.7 缓解数据倾斜- Reduce side Join转变为Map side Join

广播

  • 方案适用场景:在对RDD使用join类操作,或者是在Spark SQL中使用join语句时,而且join操作中的一inj个RDD或表的数据量比较小(比如几百M),比较适用此方案。
  • 方案实现原理:普通的join是会走shuffle过程的,而一旦shuffle,就相当于会将相同key的数据拉取到一个shuffle read task中再进行join,此时就是reduce join。但是如果一个RDD是比较小的,则可以采用广播小RDD全量数据+map算子来实现与join同样的效果,也就是map join,此时就不会发生shuffle操作,也就不会发生数据倾斜。
  • 方案优点:对join操 作导致的数据倾斜,效果非常好,因为根本就不会发生shuffle,也就根本不会发生数据倾斜。
  • 方案缺点:适用场景较少,因为这个方案只适用于一个大表和一个小表的情况。
scala> val broadcastVar = sc.broadcast(joinRdd2.collectAsMap) .
scala> joinRdd.map(x => (x._ 1, (x._ 2, broadcastVar.value.get0rElse(x._ 1,"")))).count,

2.8 缓解数据倾斜-两阶段聚合(局部聚合+全局聚合)

两阶段聚合

  • 方案适用场景:对RDD执行reduceByKey等 聚合类shuffle算子或者在Spark SQL中使用group by语句进行分组聚合时,比较适用这种方案
  • 方案实现原理:将原本相同的key通过附加随机前缀的方式,变成多个不同的key,就可以让原本被一个task处理的数据分散到多个task.上去做局部聚合,进而解决单个task处理数据量过多的问题。接着去除掉随机前缀,再次进行全局聚合,就可以得到最终的结果。
  • 方案优点:对于聚合类的shuffle操作导致的数据倾斜,效果是非常不错的。通常都可以解决掉数据倾斜,或者至少是大幅度缓解数据倾斜,将Spark作业的性能提升数倍以上。
  • 方案缺点:仅仅适用于聚合类的shuffle操作,适用范围相对较窄。如果是join类的shuffle操作,还得用其他的解决方案将相同的key的数据拆分处理。
scala> val kvRdd3 = kvRdd2.map(x=>(if (x._1 ==20001)(x._1 + scala.util.Random.nextInt(100),x._2) else x)).
scala> kvRdd3.groupByKey().count.

2.9 缓解数据倾斜-随机前缀和扩容RDD进行join

  • 方案适用场景:如果在进行join操作时,RDD中有大量的key导致数据倾斜,那么进行分拆key也没什么意义。
  • 方案实现思路:
  1. 将该RDD的每条数据都打上一个n以内的随机前缀。
  2. 同时对另外一个正常的RDD进行扩容,将每条数据都扩容成n条数据,扩容出来的每条数据都依次打上一个0~n的前缀。
  3. 最后将两个处理后的RDD进行join即可。
  4. 和上一种方案是尽量只对少数倾斜key对应的数据进行特殊处理,由于处理过程需要扩容RDD,因此上一种方案扩容RDD后对内存的占用并不大;而这一种方案是针对有大量倾斜key的情况,没法将部分key拆分出来进行单独处理,因此只能对整个RDD进行数据扩容,对内存资源要求很高。
  • 方案优点:对join类型的数据倾斜基本都可以处理,而且效果也相对比较显著,性能提升效果非常不错。
  • 方案缺点:该方案更多的是缓解数据倾斜,而不是彻底避免数据倾斜。而且需要对整个RDD进行扩容,对内存资源要求很高。
  • 方案实践经验:曾经开发一个数据需求的时候,发现一个join导致了数据倾斜。优化之前,作业的执行时间大约是60分钟左右;使用该方案优化之后,执行时间缩短到10分钟左右,性能提升了6倍。

2.10 缓解数据倾斜-过滤少数倾斜Key

  • 适用场景:如果发现导致倾斜的key就少数几个,而且对计算本身的影响并不大的
    话,那么很适合使用这种方案。比如99%的key就对应10条数据,但是只有一个key对应了100万数据,从而导致了数据倾科。
  • 方案优点:实现简单,而且效果也很好,可以完全规避掉数据倾斜。
  • 方案缺点:适用场景不多,大多数情况下,导致倾斜的key还是很多的,并不是
    只有少数几个。
  • 实践经验:在项目中我们也采用过这种方案解决数据倾斜。有一次发现某一天Spark作业在运行的时候突然OOM了,追查之后发现,是Hive表中的某一个key在那天数据异常,导致数据量暴增。因此就采取每次执行前先进行采样,计算出样本中数据量最大的几个key之后,直接在程序中将那些key给过滤掉。

3. shuffle相关参数调优

  1. spark.shuftle.file.buffer

主要是设置的SUuffle过程中写文件的缓冲,默认32k,如果内存足够,可以适当调大,来减少写入磁盘的数量。

  1. spark.reducer.maxSizeInFlight

主要是设置Shufle过程中读文件的缓冲区,一次能够读取多少数据,如果内存足够,可以适当扩大,减少整个网络传输次数。

  1. spark.shufle.io.maxRetries

主要是设置网络连接失败时,重试次数,适当调大能够增加稳定性。

  1. spark.shuftle.io.retyWait

主要设置每次重试之间的间隔时间,可以适当调大,增加程序稳定性。

  1. spark.shuftle.memoryFraction

Shufle过程中的内存占用,如果程序中较多使用了Shufle操作,那么可以适当调大该区域。

  1. spark.shufte.manager

Hash和Sort方式,Sort是默认,Hash在reduce数量比较少的时候,效率会很高。

  1. spark.shuftle.sort.bypassMergeThreshold

设置的是Sort方式中,启用Hash输出方式的临界值,如果你的程序数据不需要排序,而且reduce数量比较少,那推荐可以适当增大临界值。

  1. spark.shufle.consolidateFiles

如果你使用Hash Shutfle方式,推荐打开该配置,实现更少的文件输出。

  1. spark.sql.shuffle.partitions

JOIN或聚合等需要shuffle的操作时,设定从mapper端写出的partition个数。类似于MR中的reducer,当partition多时,产生的文件也会

  1. spark.sql.adaptive.shuffle.targetPostShuffleInputSize

当mapper端两个partition的数据合并后数据量小于targetPostShuffleInputSize时,Spark会将两个partition进行合并到一个reducer端进行处理。默认64m

  1. spark.sql.adaptive.enabled=true

spark的自适应执行,启动Adaptive Execution

  1. spark.dynamicAllocation.enabled=true

开启动态资源分配,Spark可以根据当前作业的负载动态申请和释放资源

  1. spark.dynamicAllocation.maxExecutors

开启动态资源分配后,同一时刻,最多可申请的executor个数。task较多时,可适当调大此参数,保证task能够并发执行完成,缩短作业执行时间

  1. spark.dynamicAllocation.minExecutors

某一时刻executor的最小个数。平台默认设置为3,即在任何时刻,作业都会保持至少有3个及以上的executor存活,保证任务可以迅速调度

  1. spark.sql.adaptive.minNumPostShufflePartitions

当spark.sql.adaptive.enabled参数开启后,有时会导致很多分区被合并,为了防止分区过少而影响性能。设置该参数,保障至少的shuffle分区数

  1. spark.hadoop.mapreduce.input.fileinputformat.split.maxsize
    spark.hadoop.mapreduce.input.fileinputformat.split.minsize

控制在ORC切分时stripe的合并处理。当几个stripe的大小大于设定值时,会合并到一个task中处理。适当调小该值以增大读ORC表的并发

4. 程序开发调优原则

  1. 避免创建重复数据源的RDD。
  2. 如果你需要操作的RDD的数据是另外一个RDD数据的子集,那尽量使用后面一个RDD来操作,尽量避免创建新的RDD。
 缓存:(1)dataFrame.cache  (2)sparkSession.catalog.cacheTable(“tableName”)
 释放缓存:(1)dataFrame.unpersist  (2)sparkSession.catalog.uncacheTable(“tableName”)
  1. 如果你RDD的计算过程比较长,而费时,那么如果有多个action操作使用到了该RDD,那么尽量进行RDD的缓存操作。
  2. 如果能够通过普通算子进行实现的功能,尽量避免使用Shuffle类算子进行实现。比如map side Join。
  3. 尽量使用类似ReduceByKey或者aggregateByKey这种带有map节点预聚合的操作的算子,不使用类似groupByKey这种reduce端聚合的算子,以减少Stufte数据量。
  4. 尽量使用高性能的算子:
(1) 使用reduceByKey/aggregateByKey替代groupByKey
(2) 使用mapParlitions替代普通map
(3)使用forcachPartitions替代forcach
(4)使用filter之后进行coalesce操作
(5)使用repartitionAndSortWithinPatitions替代repartition与sort类操作。ropartition.AndSortWithinPartitions是Spark官网推荐的一个算子官方建议,如果需要在repartition重分区之后,还要进行排序,建议直接使用repartitionAndSortWithinPartitions算子。
  1. 如果算子中的算法使用到了大变量,尽量通过广播变量进行使用。

  2. 序列化方面,尽量使用Kryo这种序列化方式来替代Java默认的序列化方式。

  3. 如果遇到一个RDD频繁和其他RDD进行Stufle类操作,比如cogroup()、groupWitb()、join()、letOuterJoin()、rightOuterJoin()、groupByKey()、reduceByKey()、combineByKey()以及lookup()等,那么最好将该RDD通过partitionBy操作进行预分区,这些操作在Shuff1e过程中会减少Shuff1e的数据量。

  4. 如果能够用字符串代替对象的,尽量用字符串,如果能够用Int、Long等基础类型来代替字符串的,尽量用基础变量。如果能够数组来代替类似Hashllap、LinkList这种集合类型的,尽量用数组来实现。

5.运行资源调优

  1. num-executors
    应用运行时executor的数量,推荐50-100左右比较合适。
  2. executor memory
    应用运行时Executor的内存,推荐4-8G比较合适。
  3. executor-cores
    应用运行时Executor的CPU核数,推荐2-4个比较合适。
  4. driver memory
    应用运行时Driver的内存量,主要考虑如果你用map side join或者一些类似于collect的操作,那么要相应调大内存量。
  5. spark.default.parllelism
    每个stage默认的task数量,推荐参数为num- executors * executor-cores的2~3倍较为合适,处理RDD时才会起作用,对Spark SQL的无效
  6. spark.storage.memoryFraction
    每一个Executor中用于RDD缓存的内存比例,如果你的程序中有大量的数据缓存,可以考虑调大整个比例。默认是60%
  7. spark.shufle.memoryFraction
    每一个Executor中用于Shuf1e操作的内存比例,默认是20%,如果你的程序中有大量的Shuf1e类算子,那么可以考虑调整他的比例。
  8. spark.yarn.executor.memoryOverhead
    Spark运行还需要一些堆外内存,直接向系统申请,如数据传输时的netty等。
    (spark.executor.memory+spark.yarn.executor.memoryOverhead)。适当提高该参数的值,可以有效增加程序的并发度,是作业执行的更快。不过同时也增加executor内存压力,容易出现OOM

6.其他参数调优

  1. spark.sql.autoBroadcastJoinThreshold

使用BroadcastJoin时候表的大小阈值(-1 则取消使用)

  1. spark.sql.broadcastTimeout

BroadcastJoin的等待超时的时间

  1. spark.speculation

执行任务的推测执行。这意味着如果一个或多个任务在一个阶段中运行缓慢,它们将被重新启动

  1. spark.speculation.quantile

在特定阶段启用推测之前必须完成的部分任务。推荐0.75/0.95

  1. spark.kryoserializer.buffer.max

Kryo串行缓冲区的最大允许大小(以MiB为单位)。它必须大于您尝试序列化的任何对象,并且必须小于2048m。如果在Kryo中收到“超出缓冲区限制”异常,请增加此值。推荐1024m

  1. spark.sql.optimizer.metadataOnly

启用仅使用表的元数据的元数据查询优化来生成分区列,而不是表扫描

7.常见情况

1.OOM内存溢出,Spark根据spark.executor.memory+spark.yarn.executor.memoryOverhead的值向RM申请一个容器,当executor运行时使用的内存超过这个限制时,会被yarn kill掉。失败信息为:Container killed by YARN for exceeding memory limits. XXX of YYY physical memory used. Consider boosting spark.yarn.executor.memoryOverhead。合理的调整这两个参数

2.小文件数过多,当spark执行结束后,如果生成较多的小文件可以通过hive对文件进行合并。
rc/orc文件: ALTER TABLE table_name CONCATENATE ;
其他文件:指定输出文件大小并重写表(insert overwrite table _name_new select * from table_name)

3.spark结果与hive结果不一致,数据文件字段中存在特殊字符带来的错行错列,剔除特殊字符,如: regexp_replace(name,’\n|\r|\t|\r\n|\u0001’, ‘’)
spark为了优化读取parquet格式文件,使用自己的解析方式读取数据。将该方式置为falseset spark.sql.hive.convertMetastoreParquet=false
hive中对于null和空值与spark的差异。已知的办法是调整hive的参数:serialization.null.format 如:alter table table_name set serdeproperties(‘serialization.null.format’ = ‘’)

4.实践中跑的Spark job, 有的特别慢,查看CPU利用率很低,可以尝试减少每个executor占用CPU core的数量,增加并行的executor数量,同时配合增加分片,整体上增加了CPU的利用率,加快数据处理速度。

5.发现某job很容易发生内存溢出,我们就增大分片数量,从而减少了每片数据的规模,同时还减少并行的executor数量,这样相同的内存资源分配给数量更少的executor,相当于增加了每个task的内存分配,这样运行速度可能慢了些,但是总比OOM强。

6.数据量特别少,有大量的小文件生成,就减少文件分片,没必要创建那么多task,这种情况,如果只是最原始的input比较小,一般都能被注意到但是,如果是在运算过程中,比如应用某个reduceBy或者某个filter 以后,数据大量减少,这种低效情况就很少被留意到。

参考文章:

  1. Spark性能优化指南——高级篇
    https://tech.meituan.com/2016/05/12/spark-tuning-pro.html

猜你喜欢

转载自blog.csdn.net/weixin_42526352/article/details/104981122