大神优化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的内存大小

      •  

     

猜你喜欢

转载自www.cnblogs.com/ruanjianwei/p/12133630.html