文章目录
- 1. spark基本概念
- 2. 数据倾斜优化
- 2.1 数据倾斜是什么?
- 2.2 如何定位数据倾斜?
- 2.3 数据倾斜的几种典型情况
- 2.4 缓解数据倾斜-避免数据源的数据倾斜
- 2.5 缓解数据倾斜-调整并行度
- 2.6 缓解数据倾斜-自定义Partitioner
- 2.7 缓解数据倾斜- Reduce side Join转变为Map side Join
- 2.8 缓解数据倾斜-两阶段聚合(局部聚合+全局聚合)
- 2.9 缓解数据倾斜-随机前缀和扩容RDD进行join
- 2.10 缓解数据倾斜-过滤少数倾斜Key
- 3. shuffle相关参数调优
- 4. 程序开发调优原则
- 5.运行资源调优
- 6.其他参数调优
- 7.常见情况
1. 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 如何定位数据倾斜?
- 是不是有OOM情况出现,一般是少数出现内存溢出的问题。
- 是不是应用运行时间差异很大,总体时间很长。
- 你需要了解所处理的数据Key的分布情况,如果有些Key具有大量的数据,那么就要小心数据倾斜的问题。
- 一般需要通过Spark Web UI和其他一些些监控方式中出现的异常来综合判断。
- 看看你的代码里面是否有一些导致Shuffle的算子出现
2.3 数据倾斜的几种典型情况
- 数据源中的数据分布不匀,Spark需要频繁交互
- 数据集中的不同Key由于分区方式,导致数据倾斜
- JOIN操作中,一个数据集中的数据分布不均匀,另一个数据集较小
- 聚合操作中,数据集中的数据分布不均匀
- JOIN操作中,两个数据集都比较大,其中某一数据集中只有几个Key的数据分布不均匀
- JOIN操作中,两个数据集都比较大,其中某一数据集中有很多Key的数据分布不均匀
- 数据集中少数几个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也没什么意义。
- 方案实现思路:
- 将该RDD的每条数据都打上一个n以内的随机前缀。
- 同时对另外一个正常的RDD进行扩容,将每条数据都扩容成n条数据,扩容出来的每条数据都依次打上一个0~n的前缀。
- 最后将两个处理后的RDD进行join即可。
- 和上一种方案是尽量只对少数倾斜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相关参数调优
- spark.shuftle.file.buffer
主要是设置的SUuffle过程中写文件的缓冲,默认32k,如果内存足够,可以适当调大,来减少写入磁盘的数量。
- spark.reducer.maxSizeInFlight
主要是设置Shufle过程中读文件的缓冲区,一次能够读取多少数据,如果内存足够,可以适当扩大,减少整个网络传输次数。
- spark.shufle.io.maxRetries
主要是设置网络连接失败时,重试次数,适当调大能够增加稳定性。
- spark.shuftle.io.retyWait
主要设置每次重试之间的间隔时间,可以适当调大,增加程序稳定性。
- spark.shuftle.memoryFraction
Shufle过程中的内存占用,如果程序中较多使用了Shufle操作,那么可以适当调大该区域。
- spark.shufte.manager
Hash和Sort方式,Sort是默认,Hash在reduce数量比较少的时候,效率会很高。
- spark.shuftle.sort.bypassMergeThreshold
设置的是Sort方式中,启用Hash输出方式的临界值,如果你的程序数据不需要排序,而且reduce数量比较少,那推荐可以适当增大临界值。
- spark.shufle.consolidateFiles
如果你使用Hash Shutfle方式,推荐打开该配置,实现更少的文件输出。
- spark.sql.shuffle.partitions
JOIN或聚合等需要shuffle的操作时,设定从mapper端写出的partition个数。类似于MR中的reducer,当partition多时,产生的文件也会
- spark.sql.adaptive.shuffle.targetPostShuffleInputSize
当mapper端两个partition的数据合并后数据量小于targetPostShuffleInputSize时,Spark会将两个partition进行合并到一个reducer端进行处理。默认64m
- spark.sql.adaptive.enabled=true
spark的自适应执行,启动Adaptive Execution
- spark.dynamicAllocation.enabled=true
开启动态资源分配,Spark可以根据当前作业的负载动态申请和释放资源
- spark.dynamicAllocation.maxExecutors
开启动态资源分配后,同一时刻,最多可申请的executor个数。task较多时,可适当调大此参数,保证task能够并发执行完成,缩短作业执行时间
- spark.dynamicAllocation.minExecutors
某一时刻executor的最小个数。平台默认设置为3,即在任何时刻,作业都会保持至少有3个及以上的executor存活,保证任务可以迅速调度
- spark.sql.adaptive.minNumPostShufflePartitions
当spark.sql.adaptive.enabled参数开启后,有时会导致很多分区被合并,为了防止分区过少而影响性能。设置该参数,保障至少的shuffle分区数
- spark.hadoop.mapreduce.input.fileinputformat.split.maxsize
spark.hadoop.mapreduce.input.fileinputformat.split.minsize
控制在ORC切分时stripe的合并处理。当几个stripe的大小大于设定值时,会合并到一个task中处理。适当调小该值以增大读ORC表的并发
4. 程序开发调优原则
- 避免创建重复数据源的RDD。
- 如果你需要操作的RDD的数据是另外一个RDD数据的子集,那尽量使用后面一个RDD来操作,尽量避免创建新的RDD。
缓存:(1)dataFrame.cache (2)sparkSession.catalog.cacheTable(“tableName”)
释放缓存:(1)dataFrame.unpersist (2)sparkSession.catalog.uncacheTable(“tableName”)
- 如果你RDD的计算过程比较长,而费时,那么如果有多个action操作使用到了该RDD,那么尽量进行RDD的缓存操作。
- 如果能够通过普通算子进行实现的功能,尽量避免使用Shuffle类算子进行实现。比如map side Join。
- 尽量使用类似ReduceByKey或者aggregateByKey这种带有map节点预聚合的操作的算子,不使用类似groupByKey这种reduce端聚合的算子,以减少Stufte数据量。
- 尽量使用高性能的算子:
(1) 使用reduceByKey/aggregateByKey替代groupByKey
(2) 使用mapParlitions替代普通map
(3)使用forcachPartitions替代forcach
(4)使用filter之后进行coalesce操作
(5)使用repartitionAndSortWithinPatitions替代repartition与sort类操作。ropartition.AndSortWithinPartitions是Spark官网推荐的一个算子官方建议,如果需要在repartition重分区之后,还要进行排序,建议直接使用repartitionAndSortWithinPartitions算子。
- 如果算子中的算法使用到了大变量,尽量通过广播变量进行使用。
- 序列化方面,尽量使用Kryo这种序列化方式来替代Java默认的序列化方式。
- 如果遇到一个RDD频繁和其他RDD进行Stufle类操作,比如cogroup()、groupWitb()、join()、letOuterJoin()、rightOuterJoin()、groupByKey()、reduceByKey()、combineByKey()以及lookup()等,那么最好将该RDD通过partitionBy操作进行预分区,这些操作在Shuff1e过程中会减少Shuff1e的数据量。
- 如果能够用字符串代替对象的,尽量用字符串,如果能够用Int、Long等基础类型来代替字符串的,尽量用基础变量。如果能够数组来代替类似Hashllap、LinkList这种集合类型的,尽量用数组来实现。
5.运行资源调优
- num-executors
应用运行时executor的数量,推荐50-100左右比较合适。 - executor memory
应用运行时Executor的内存,推荐4-8G比较合适。 - executor-cores
应用运行时Executor的CPU核数,推荐2-4个比较合适。 - driver memory
应用运行时Driver的内存量,主要考虑如果你用map side join或者一些类似于collect的操作,那么要相应调大内存量。 - spark.default.parllelism
每个stage默认的task数量,推荐参数为num- executors * executor-cores的2~3倍较为合适,处理RDD时才会起作用,对Spark SQL的无效 - spark.storage.memoryFraction
每一个Executor中用于RDD缓存的内存比例,如果你的程序中有大量的数据缓存,可以考虑调大整个比例。默认是60% - spark.shufle.memoryFraction
每一个Executor中用于Shuf1e操作的内存比例,默认是20%,如果你的程序中有大量的Shuf1e类算子,那么可以考虑调整他的比例。 - spark.yarn.executor.memoryOverhead
Spark运行还需要一些堆外内存,直接向系统申请,如数据传输时的netty等。
(spark.executor.memory+spark.yarn.executor.memoryOverhead)。适当提高该参数的值,可以有效增加程序的并发度,是作业执行的更快。不过同时也增加executor内存压力,容易出现OOM
6.其他参数调优
- spark.sql.autoBroadcastJoinThreshold
使用BroadcastJoin时候表的大小阈值(-1 则取消使用)
- spark.sql.broadcastTimeout
BroadcastJoin的等待超时的时间
- spark.speculation
执行任务的推测执行。这意味着如果一个或多个任务在一个阶段中运行缓慢,它们将被重新启动
- spark.speculation.quantile
在特定阶段启用推测之前必须完成的部分任务。推荐0.75/0.95
- spark.kryoserializer.buffer.max
Kryo串行缓冲区的最大允许大小(以MiB为单位)。它必须大于您尝试序列化的任何对象,并且必须小于2048m。如果在Kryo中收到“超出缓冲区限制”异常,请增加此值。推荐1024m
- 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 以后,数据大量减少,这种低效情况就很少被留意到。
参考文章:
- Spark性能优化指南——高级篇
https://tech.meituan.com/2016/05/12/spark-tuning-pro.html