大神优化spark
1.资源优化
-
部署spark集群中指定资源分配的默认参数
-
SPARK_WORKER_CORES 核数
-
SPARK_WORKER_MEMORY 内存大小
-
SPARK_WORKER_INSTANCES 每台机器启动worker数
-
-
在提交application时给当前的application分配更多的资源
-
提交命令选项
-
--executor-cores
-
--executor-memory
-
--total-executor-cores
-
-
配置信息
-
spark.executor.cores
-
spark.executor.memory
-
spark.max.cores
-
-
动态分配资源
-
spark.shuffle.service.enabled true //启用External shuffle Service服务
-
spark.shuffle.service.port 7337 //Shuffle Service服务端口,必须和yarn-site中的一致
-
spark.dynamicAllocation.enabled true //开启动态资源分配
-
spark.dynamicAllocation.minExecutors 1 //每个Application最小分配的executor数
-
spark.dynamicAllocation.maxExecutors 30 //每个Application最大并发分配的executor数
-
spark.dynamicAllocation.schedulerBacklogTimeout 1s
-
spark.dynamicAllocation.sustainedSchedulerBacklogTimeout 5s
-
-
2.并行度优化
-
读取HDFS中的数据时,降低block大小,相当于提高了RDD中partition的个数,也可以在读取文件时设置分区数sc.textFile(xx,numPartitions)
-
sc.parallelize(xxx, numPartitions)
-
sc.makeRDD(xxx, numPartitions)
-
sc.parallelizePairs(xxx, numPartitions)
-
repartions/coalesce
-
redecByKey/groupByKey/join ---(xxx, numPartitions)
-
spark.default.parallelism net set
-
spark.sql.shuffle.partitions---200
-
自定义分区器
-
如果读取数据是在sparkstreaming中
-
Receiver: spark.streaming.blockInterval—200ms
-
Direct:读取的topic的分区数
-
3,代码优化
-
避免创建重复的RDD
-
对多次使用的RDD进行持久化
-
尽量避免使用shuffle类的算子
-
对于一个RDD大.一个RDD小进行join操作时,可以将小的RDD用广播变量广播
-
-
使用map-side预聚合的shuffle操作
-
尽量使用有combiner的shuffle类算子(combiner的概念-->在map端,每个map task计算完毕后进行的局部聚合)
-
优点
-
降低shuffle write写磁盘的数据量
-
降低shuffle read拉取数据量的大小
-
降低reduce端聚合的次数
-
-
含有combiner的shuffle类算子
-
reduceByKey,在某些场景中可以使用reduceByKey代替groupByKey
-
aggregateByKey
-
combinerByKey
-
-
-
尽量使用高性能的算子
-
使用reduceByKey替代groupByKey
-
使用mapPartition替代map
-
使用foreachPartition替代foreach
-
filter后使用coalesce减少分区数
-
使用repartitionAndSortWithinPartitions替代repartition与sort类操作
-
使用repartition和coalesce算子操作分区
-
-
使用广播变量
-
需要在算子函数中使用外部变量的场景下运用广播功能来提升性能
-
若变量本身比较大,那么大量的变量副本在网络中传输的性能开销,以及在各个节点的executor中占用过多内存导致频繁的GC,都会极大的影响性能,建议使用广播功能,对该变量进行广播
-
广播后的变量,会保证每个executor的内存中,只主流一份变量副本,而executor中的task执行时共享该executor中的那份变量副本,这样可以大大减少变量副本的数量,从而减少网络传输的性能开销,以及减少对executor内存的占用开销,降低GC的频率
-
4,使用Kryo优化序列化性能
-
spark中用到序列化的地方
-
算子函数使用到外部变量时,该变量会被序列化后进行网络传输
-
将自定义类型作为RDD的泛型类型时,所有自定义类型对象,都会进行序列化,也要求自定义类必须实现Serializable接口
-
使用可序列化的持久化策略时,要用到序列化
-
-
Kryo序列化器介绍
-
Kryo序列化机制,比默认的Java序列化机制,速度要快,序列化后的数据要更小,大概是Java序列化机制的1/10。所以Kryo序列化优化以后,可以让网络传输的数据变少;在集群中耗费的内存资源大大减少.对于spark中出现的这三种情况都可以运用Kryo机制进行序列化
-
Spark之所以默认没有使用Kryo作为序列化类库,是因为Kryo要求最好要注册所有需要进行序列化的自定义类型,因此对于开发者来说,这种方式比较麻烦。
-
-
优化数据结构
-
java中三种类型比较消耗内存
-
对象,比较占用空间
-
字符串
-
集合类型
-
-
在Spark编码实现中,特别是对于算子函数中的代码,尽量不要使用上述三种数据结构,尽量使用字符串替代对象,使用原始类型(比如Int、Long)替代字符串,使用数组替代集合类型,这样尽可能地减少内存占用,从而降低GC频率,提升性能。
-
-
使用高性能的库fastutil
-
fastutil是扩展了Java标准集合框架(Map、List、Set;HashMap、ArrayList、HashSet)的类库,提供了特殊类型的map、set、list和queue;fastutil能够提供更小的内存占用,更快的存取速度;我们使用fastutil提供的集合类,来替代自己平时使用的JDK的原生的Map、List、Set,好处在于,fastutil集合类,可以减小内存的占用,并且在进行集合的遍历、根据索引(或者key)获取元素的值和设置元素的值的时候,提供更快的存取速度。fastutil的每一种集合类型,都实现了对应的Java中的标准接口(比如fastutil的map,实现了Java的Map接口),因此可以直接放入已有系统的任何代码中。
-
fastutil最新版本要求Java 7以及以上版本。
-
5.数据本地化
-
数据本地化的级别
-
PROCESS_LOCAL
-
task要计算的数据在本进程(executor)内存中
-
-
NODE_LOCAL
-
task所计算的数据在本节点所在的磁盘上
-
task所计算的数据在本节点其他executor进程的内存中
-
-
NO_PREF
-
task所计算的数据在关系型数据库中
-
-
RACK_LOCAL
-
task所计算的数据在同机架的不同节点的磁盘或者executor进程的内存中
-
-
ANY
-
跨机架
-
-
-
spark数据本地化调优
-
可以增加每次发送task的等待时间(默认都是3s),将3s倍数调大
-
等待时间不能调大很大,调整数据本地化的级别不要本末倒置,虽然每一个task的本地化级别是最高了,但整个Application的执行时间反而加长。
-
-
查看数据本地化的级别
-
通过日志或者WEBUI
-
-
内存调优
-
JVM堆内存分为一块较大的Eden和两块较小的survivor,每次只使用Eden和其中一块survivor当回收时将Eden和Survivor中还存活着的对象一次性复制到另外一块Survivor上,最后清理掉Eden和刚才用过的Survivor。也就是说当task创建出来对象会首先往Eden和survivor1中存放,survivor2是空闲的,当Eden和survivor1区域放满以后就会触发minor gc小型垃圾回收,清理掉不再使用的对象。会将存活下来的对象放入survivor2中。
-
如果存活下来的对象大小大于survivor2的大小,那么JVM就会将多余的对象直接放入到老年代中
-
默认15岁,垃圾回收还是没有回收回去就会跑到老年代里面去了。
这样会导致在老年代中存放大量的短生命周期的对象,老年代应该存放的是数量比较少并且会长期使用的对象,比如数据库连接池对象。这样的话,老年代就会满溢(full gc 因为本来老年代中的对象很少,很少进行full gc 因此采取了不太复杂但是消耗性能和时间的垃圾回收算法)。不管minor gc 还是 full gc都会导致JVM的工作线程停止。
-
堆内存不足产生的原因
-
频繁的minor gc
-
老年代中大量的短生命周期的对象会导致full gc。
-
gc 多了就会影响Spark的性能和运行的速度。
-
-
JVM调优主要是降低gc时间,可以修改Executor内存的比例参数。
-
提高executor总体内存的大小
-
降低储存内存比例或者降低shuffle聚合内存比例
-
-
shuffle调优
-
buffer大小——32KB
-
shuffle read拉取数据量的大小——48M
-
shuffle聚合内存的比例——20%
-
拉取数据重试次数——5次
-
重试间隔时间60s
-
Spark Shuffle的种类
-
SortShuffle bypass机制 200次
-
-
调节executor的堆外内存
-
spark默认的网络连接的超时时长是120s;如果卡住120s都无法建立连接的话,那么这个task就失败了。
-
在./spark-submit提交任务的脚本里面添加:
--conf spark.core.connection.ack.wait.timeout=300
-
-
6.解决数据倾斜
-
使用hive ETL预处理数据
-
ETL预先对数据按照key进行聚合,或者是预先和其他表进行join),然后在Spark作业中针对的数据源就不是原来的Hive表了,而是预处理后的Hive表。此时由于数据已经预先进行过聚合或join操作了,那么在Spark作业中也就不需要使用原先的shuffle类算子执行这类操作了。这种方式属于治标不治本
-
-
过滤少数导致倾斜的key
-
如果我们判断那少数几个数据量特别多的key,对作业的执行和计算结果不是特别重要的话,那么干脆就直接过滤掉那少数几个key。比如,在Spark SQL中可以使用where子句过滤掉这些key或者在Spark Core中对RDD执行filter算子过滤掉这些key。如果需要每次作业执行时,动态判定哪些key的数据量最多然后再进行过滤,那么可以使用sample算子对RDD进行采样,然后计算出每个key的数量,取数据量最多的key过滤掉即可。
-
-
提高shuffle操作的并行度
-
增加shuffle read task的数量,可以让原本分配给一个task的多个key分配给多个task,从而让每个task处理比原来更少的数据。举例来说,如果原本有5个不同的key,每个key对应10条数据,这5个key都是分配给一个task的,那么这个task就要处理50条数据。而增加了shuffle read task以后,每个task就分配到一个key,即每个task就处理10条数据,那么自然每个task的执行时间都会变短了。
-
-
双重聚合
-
将原本相同的key通过附加随机前缀的方式,变成多个不同的key,就可以让原本被一个task处理的数据分散到多个task上去做局部聚合,进而解决单个task处理数据量过多的问题。接着去除掉随机前缀,再次进行全局聚合,就可以得到最终的结果。
-
图例讲解
-
-
-
使用广播变量代替join
-
普通的join是会走shuffle过程的,而一旦shuffle,就相当于会将相同key的数据拉取到一个shuffle read task中再进行join,此时就是reduce join。但是如果一个RDD是比较小的,则可以采用广播小RDD全量数据+map/filter算子来实现与join同样的效果,也就是map join,此时就不会发生shuffle操作,也就不会发生数据倾斜。
-
-
采样倾斜可以并分拆join操作
-
对包含少数几个数据量过大的key的那个RDD,通过sample算子采样出一份样本来,然后统计一下每个key的数量,计算出来数据量最大的是哪几个key。然后将这几个key对应的数据从原来的RDD中拆分出来,形成一个单独的RDD,并给每个key都打上n以内的随机数作为前缀,而不会导致倾斜的大部分key形成另外一个RDD。接着将需要join的另一个RDD,也过滤出来那几个倾斜key对应的数据并形成一个单独的RDD,将每条数据膨胀成n条数据,这n条数据都按顺序附加一个0~n的前缀,不会导致倾斜的大部分key也形成另外一个RDD。再将附加了随机前缀的独立RDD与另一个膨胀n倍的独立RDD进行join,此时就可以将原先相同的key打散成n份,分散到多个task中去进行join了。而另外两个普通的RDD就照常join即可。最后将两次join的结果使用union算子合并起来即可,就是最终的join结果 。
-
图例讲解
-
-
-
使用随机前缀和扩容RDD进行join
-
该方案的实现思路基本和“解决方案六”类似,首先查看RDD/Hive表中的数据分布情况,找到那个造成数据倾斜的RDD/Hive表,比如有多个key都对应了超过1万条数据。然后将该RDD的每条数据都打上一个n以内的随机前缀。同时对另外一个正常的RDD进行扩容,将每条数据都扩容成n条数据,扩容出来的每条数据都依次打上一个0~n的前缀。最后将两个处理后的RDD进行join即可。
-
图例讲解
-
-
7.spark故障解决
-
shuffle file cannot find:磁盘小文件找不到
-
connection timeout ----shuffle file cannot find
-
提高建立连接的超时时间,或者降低gc,降低gc了那么spark不能堆外提供服务的时间就少了,那么超时的可能就会降低。
-
-
1) fetch data fail ---- shuffle file cannot find
-
提高拉取数据的重试次数以及间隔时间。
-
-
1) OOM/executor lost ---- shuffle file cannot find
-
提高堆外内存大小,提高堆外内存大小。
-
-
-
reduce OOM
-
BlockManager拉取的数据量大,reduce task处理的数据量小
-
解决方法\
-
降低每次拉取的数据量
-
提高shuffle聚合的内存比例
-
提高executor的内存大小
-
-
-