Spark 任务如何调优

  1. spark 性能调优
    a. 分配更多资源——第一步要做的

    比如增加 executor个数(num_executor)、增加 executor 的 cpu 核数(executor_cores)、增加 executor 的内存量(executor_memory)

    增加 executor个数 和 executor 的 cpu 核数是为了增加执行的并行能力(能够并行执行的task数量也变多了);

    增加 executor 的内存量:无论是对RDD进行cache,还是shuffle操作的reduce端,都是需要大量的内存来缓存数据,内存不够,会有大量的磁盘IO操作;对于task的执行,可能会创建很多对象。内存不够,能会频繁导致JVM堆内存满了,然后频繁GC

    b. 提高 spark 运行的并行度

    并行度:Spark作业中,各个stage的task数量executor个数 * executor 的 cpu 核数
    合理设置并行度,就可以完全充分利用你的集群计算资源,并且减少每个task要处理的数据量,最终,就是提升你的整个Spark作业的性能和运行速度。

    task数量,至少设置成与Spark application的总cpu core数量相同(最理想情况,比如总共150个cpu core,分配了150个task,一起运行,差不多同一时间运行完毕)

    官方是推荐,task数量,设置成spark application总cpu core数量的2~3倍,比如150个cpu core,基本要设置task数量为300~500(一个task运行完了以后,另一个task马上可以补上来,就尽量让cpu core不要空闲

    c. 重构 RDD 架构以及 RDD 持久化

    尽量去复用RDD差不多的RDD可以抽取成一个共同的RDD公共RDD一定要实现持久化

    d. 使用广播变量

    task执行的算子中,使用了外部的变量,(比如说map就是比较消耗内存的数据格式),每个task都会获取一份变量的副本,会造成不必要的内存的消耗和占用

    广播变量的好处不是每个task一份变量副本,而是变成每个节点的executor才一份副本

    e. 使用 Kryo 序列化

    默认情况下,Spark内部是使用Java的序列化机制处理起来比较方便
    缺点:默认的序列化机制的效率不高序列化的速度比较慢;序列化以后的数据,占用的内存空间相对还是比较大

    Spark支持使用Kryo序列化机制,比默认的Java序列化机制,速度要快序列化后的数据要更小

    f. 使用 fastutil 集合优化数据格式代替java中的Array、List、Set、Map)

    提供更小的内存占用,更快的存取速度

    g. 调节数据本地化等待时长(s)

    调节数据本地化等待时长,想要task和数据在一个节点上,直接从本地 executorBlockManager 中获取数据,减少磁盘IO开销大量网络传输

  2. JVM 调优

    a. 降低cache操作的内存占比

    Spark中,堆内存又被划分成了两块儿,一块儿是专门用来给RDD的cache、persist操作进行RDD数据缓存用的;另外一块儿,给spark算子函数的运行使用的,存放函数中自己创建的对象

    默认情况下,给RDD cache操作的内存占比,是0.6,60%的内存都给了cache操作了。但是如果某些情况下,cache不是那么的紧张,问题在于task算子函数中创建的对象过多,然后内存又不太大,导致了频繁gc,导致spark频繁的停止工作

    降低cache操作的内存占比,让task执行算子函数时,有更多的内存可以使用。

    b. 调节executor堆外内存,调节连接的超时时长

    用于解决 executor 堆外内存不太够用,导致 executor 在运行的过程中,可能会内存溢出;以及gc引起的连接超时的问题

  3. Shuffle 调优

    a. 合并map端输出文件

    只有并行执行的task会去创建新的输出文件;下一批并行执行的task,就会去复用之前已有的输出文件

    b. 调整map端内存缓冲,reduce端内存占比

    数据量比较大的情况下,map 端和 reduce 端都会磁盘IO频繁,影响性能

    map task内存缓冲变大了,减少spill到磁盘文件的次数;reduce端聚合内存变大了,减少spill到磁盘的次数,而且减少了后面聚合读取磁盘文件的数量

    c. shuffle 调优之 HashShuffleManager 与 SortShuffleManager

    SortShuffleManager与HashShuffleManager两点不同

    1)SortShuffleManager会对每个reduce task要处理的数据,进行排序(默认的)。

    2)SortShuffleManager会避免像HashShuffleManager那样,默认就去创建多份磁盘文件一个task,只会写入一个磁盘文件,不同reduce task的数据,用offset来划分界定。

    自己可以设定一个阈值,默认是200,当reduce task数量少于等于200map task创建的输出文件小于等于200的;最后会将所有的输出文件合并为一份文件

    这样做的好处,就是避免了sort排序节省了性能开销。而且还能将多个reduce task的文件合并成一份文件。节省了reduce task拉取数据的时候的磁盘IO的开销

  4. 算子调优

    a. 使用mapPartition提升map类操作的性能

    数据量不是特别大的时候,都可以用这种MapPartition系列操作,性能还是非常不错的。

    如果是普通的map,比如一个partition中有1万条数据;那么你的function要执行和计算1万次。

    但是,使用MapPartitions操作之后,一个task仅仅会执行一次functionfunction一次接收所有的partition数据

    普通的map操作通常不会导致内存的OOM异常

    但是MapPartitions操作,对于大量数据来说,比如甚至一个partition100万数据,一次传入一个function以后,那么可能一下子内存不够内存溢出

    b. filter过后使用coalesce减少分区数量

    经过了这种filter之后,RDD中的每个partition的数据量可能都不太一样了

    coalesce算子,主要就是用于在filter操作之后,针对每个partition的数据量各不相同的情况,来压缩partition的数量,而且让每个partition的数据量都尽量均匀紧凑。从而便于后面的task进行计算操作,在某种程度上,能够一定程度的提升性能

    c. 使用foreachPartition优化写数据性能

    foreach算子:对于每条数据,都要单独去调用一次function。如果100万条数据,(一个partition),调用100万次。性能比较差。

    如果每个数据,你都去创建一个数据库连接的话,那么你就得创建100万次数据库连接

    多次通过数据库连接,往数据库(MySQL)发送一条SQL语句。如果有100万条数据,那么就是100万次发送SQL语句。

    数据库连接多次发送SQL语句,都是非常消耗性能的。

    foreachPartition算子一次传入一个partition所有的数据;主要创建或者获取一个数据库连接就可以;只要向数据库发送一次SQL语句和多组参数即可;

    d. 使用repartition解决sparkSql低并行度的性能问题

    Spark SQL自己会默认根据hive表对应的hdfs文件block,自动设置Spark SQL查询所在的那个stage并行度。你自己通过spark.default.parallelism参数指定的并行度,只会在没有Spark SQL的stage中生效。

    repartition算子,你用Spark SQL这一步的并行度和task数量,肯定是没有办法去改变了。但是呢,可以将你用Spark SQL查询出来的RDD,使用repartition算子,去重新进行分区,比如从20个partition,分区成100个,就可以避免只能使用少量的task处理大量数据

    e. reduceByKey替换groupByKey实现map端预聚合

    reduceByKey,相较于普通的shuffle操作(比如groupByKey),它的一个特点,就是会进行map端的本地聚合

    map端下个stage每个task创建的输出文件中,写数据之前,就会进行本地的combiner操作。(减少磁盘IO下一个stage,拉取数据的量减少reduce端数据缓存的内存占用减少reduce端聚合的数据量减少

  5. 数据倾斜调优

    a. 预聚合源数据,对hive源表提前进行聚合操作,在hive聚合之后,spark任务再去读取

    b. 检查倾斜的key是否是脏数据,可以提前过滤

    c. 提高shuffle操作reduce的并行度

    reduce task的数量变多,就可以让每个reduce task分配到更少的数据量

    d. 使用随机key实现双重聚合随机前缀

    第一次聚合的时候,在导致倾斜的 key 的前面加上 1-n 随机前缀,这样原本相同的key会分成多个组,进行局部聚合
    第二次聚合的时候,去掉key的随机前缀,进行全局聚合

    groupByKey、reduceByKey造成的数据倾斜,有比较好的效果。

    e. 将reduce端 join转换成map端 join

    如果两个RDD要进行join,其中一个RDD是比较小的。一个RDD是100万数据,一个RDD是1万数据。

    broadcast出去那个小RDD的数据以后,就会在每个executor的block manager中都驻留一份。要确保你的内存足够存放那个小RDD中的数据

    两个RDD都比较大,就不适合了。

    f. sample采样倾斜key,单独进行join后在union(再合并)

    发生数据倾斜的 key(可以采用countByKey()的方式,你可以看一下这个 RDD 各个 key 对应的数据量),单独拉出来,放到一个 RDD 中去;就用这个原本会倾斜的 key RDD其他 RDD,单独去 join 一下,这个时候,key 对应的数据,可能就会分散到多个 task 中去进行 join 操作,最后将 join 后的表进行 union 操作(再合并)

    如果一个 RDD 中,导致数据倾斜的 key,特别多;那么此时,最好还是不要这样了;

    g. 使用随机数以及扩容表进行join

    a) 选择一个RDD,要用flatMap,进行扩容,将每条数据,映射为多条数据,每个映射出来的数据,都带了一个n以内的随机数,通常来说,会选择10

    b) 将另外一个RDD,做普通的map映射操作,每条数据,都打上一个10以内的随机数

    c) 最后,将两个处理后的RDD,进行join操作

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