spark 调优

调优原则

1.保证stage划分合理(使用的算子最优),保证每个stage中的总task数合理(使每个stage分区数最优),保证每个stage最多能同时运行的task数合理(使提交参数资源分配最优),保证每个task处理的数据量合理(使数据key的分布最优)。

2. spark-submit 提交参数设置:

./bin/spark-submit \
  --master spark://192.168.1.1:7077 \
  --num-executors 100 \ (所占队列的1/3-1/4)
  --executor-memory 6G \(4-8G)
  --executor-cores 4 \ (1/4 * executor-memory )
  -- 
 ##standalone default all cores 
  --driver-memory 1G \ (有collect操作把数据collect到driver端或者广播表时需要调大)
  --conf spark.default.parallelism=1000 \ (2-3倍*executor-cores*num-executors,充分利用执行快的task占用的core)
  --conf spark.storage.memoryFraction=0.5 \ (如果Spark作业中,有较多的RDD持久化操作,该参数的值可以适当提高一些,避免内存不够缓存所有的数据,导致数据只能写入磁盘中,降低了性能。但是如果Spark作业中的shuffle类操作比较多,而持久化操作比较少,那么这个参数的值适当降低一些比较合适。此外,如果发现作业由于频繁的gc导致运行缓慢(通过spark web ui可以观察到作业的gc耗时),意味着task执行用户代码的内存不够用,那么同样建议调低这个参数的值。)
  --conf spark.shuffle.memoryFraction=0.3 \ (与上述相反)
  --conf spark.sql.auto.repartition=true (每个stage[阶段]运行时分区并不尽相同,使用此配置可优化计算后分区数,避免分区数过大导致单个分区数据量过少,每个task运算分区数据时时间过短,从而导致task频繁调度消耗过多时间)
  --conf spark.sql.autoBroadcastJoinThreshold=30M (默认10M) 
  --conf park.sql.adaptive.enabled=true
  --conf spark.sql.adaptive.minNumPostShufflePartitions=400(当开启调整partition功能后,有时会导致很多分区被合并,为了防止分区过少,可以设置该参数,防止分区过少影响性能)
  --conf spark.sql.shuffle.partitions=400 (shuffle read task的并行度,默认设置200)
  --conf spark.sql.windowExec.buffer.spill.threshold=200000 (默认是40960条)



以下列表中动态资源分配相关不建议使用
//1.下列Hive参数对Spark同样起作用。
set hive.exec.dynamic.partition=true; // 是否允许动态生成分区
set hive.exec.dynamic.partition.mode=nonstrict; // 是否容忍指定分区全部动态生成
set hive.exec.max.dynamic.partitions = 100; // 动态生成的最多分区数

//2.运行行为
set spark.sql.autoBroadcastJoinThreshold; // 大表 JOIN 小表,小表做广播的阈值
set spark.dynamicAllocation.enabled; // 开启动态资源分配
set spark.dynamicAllocation.maxExecutors; //开启动态资源分配后,最多可分配的Executor数
set spark.dynamicAllocation.minExecutors; //开启动态资源分配后,最少可分配的Executor数
set spark.sql.shuffle.partitions; // 需要shuffle是mapper端写出的partition个数
set spark.sql.adaptive.enabled; // 是否开启调整partition功能,如果开启,spark.sql.shuffle.partitions设置的partition可能会被合并到一个reducer里运行
set spark.sql.adaptive.shuffle.targetPostShuffleInputSize; //开启spark.sql.adaptive.enabled后,两个partition的和低于该阈值会合并到一个reducer
set spark.sql.adaptive.minNumPostShufflePartitions; // 开启spark.sql.adaptive.enabled后,最小的分区数
set spark.hadoop.mapreduce.input.fileinputformat.split.maxsize; //当几个stripe的大小大于该值时,会合并到一个task中处理

//3.executor能力
set spark.executor.memory; // executor用于缓存数据、代码执行的堆内存以及JVM运行时需要的内存
set spark.yarn.executor.memoryOverhead; //Spark运行还需要一些堆外内存,直接向系统申请,如数据传输时的netty等。
set spark.sql.windowExec.buffer.spill.threshold; //当用户的SQL中包含窗口函数时,并不会把一个窗口中的所有数据全部读进内存,而是维护一个缓存池,当池中的数据条数大于该参数表示的阈值时,spark将数据写到磁盘
set spark.executor.cores; //单个executor上可以同时运行的task数

1.pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.unisinsight</groupId>
    <artifactId>spark-demo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <spark.version>2.1.0</spark.version>
        <scala.version>2.11</scala.version>
    </properties>

    <dependencies>
    <dependency>
        <groupId>org.apache.spark</groupId>
        <artifactId>spark-core_2.11</artifactId>
        <version>2.1.0</version>
    </dependency>
    <!--<dependency>-->
        <!--<groupId>org.apache.spark</groupId>-->
        <!--<artifactId>spark-streaming_2.11</artifactId>-->
        <!--<version>2.1.0</version>-->
    <!--</dependency>-->
        <!-- https://mvnrepository.com/artifact/org.apache.spark/spark-sql -->
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-sql_2.11</artifactId>
            <version>2.1.0</version>
        </dependency>

        <dependency>
        <groupId>org.apache.spark</groupId>
        <artifactId>spark-hive_${scala.version}</artifactId>
        <version>2.1.0</version>
    </dependency>
    <!--<dependency>-->
        <!--<groupId>org.apache.spark</groupId>-->
        <!--<artifactId>spark-mllib_${scala.version}</artifactId>-->
        <!--<version>2.1.0</version>-->
    <!--</dependency>-->
    </dependencies>

</project>

2.dologic1

/*
 * www.unisinsight.com Inc.
 * Copyright (c) 2018 All Rights Reserved
 */
package com.unisinsight.sparkDemo

;

/**
  * description
  * sparksql执行过程中分区数的变化探索
  *
  * @ author yuwei [yu.wei@unisinsight.com]
  * @ date 2018/12/26 14:31
  *
  * @since 1.0
  */

import java.sql.Timestamp

import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.sql.{DataFrame, SQLContext, SaveMode, SparkSession}
import org.apache.spark.sql.expressions.Window
import org.apache.spark.sql.functions._
import org.apache.spark.storage.StorageLevel


object Dologic1 {

  def main(args: Array[String]): Unit = {


    /*
        val spark = new SparkConf().setAppName("spark-demo")
        .setMaster("local")
        val sc = new SparkContext(spark)
        val sqlContext = new SQLContext(sc)
        val df = sqlContext.sparkSession.createDataFrame(Seq((1, "zhangsan", 24), (2, "lsisi", 25)))
          .toDF("id", "name", "age")
    */
    val sparkSession = SparkSession.builder
      .master("local[*]")
      .appName("appName")
      // 不管是否设置了.master("local[*]")或者设置了.master("yarn-client")
      // 如果设置了spark.default.parallelism,则按该值作为初始并行度、分区数、最多可同时执行的task数。
      // 如果没设置,但设置了.master("local[*]"),则按本地core个数作为初始值。
      //如果没设置,但设置了.master("yarn-client"),则按分配的:num=执行器个数*每个执行器core个数 作为初始值。
      .config("spark.default.parallelism", 1)
      //设置shuffle操作的并行度,默认是200,shuffle结束后会生成200个分区。
      //如果遇到数据倾斜或者shuffle太慢,可以适当增加这个值,尽量将发生数据倾斜的多个key分布在不同分区。
      // 如果某个key对应的数据量实在太大,程序因为那一个key就发生数据倾斜,则这个方法无能为力。
      .config("spark.sql.shuffle.partitions",200)
      .enableHiveSupport()
      .getOrCreate()
    import sparkSession.implicits._
    val df = sparkSession.createDataFrame(Seq((1, "zhangsan", 24), (2, "lsisi", 25),
      (54, "zhangewsan", 24), (44365, "lsisi", 25),(1, "zhangsan", 24), (2, "lsisi", 25),
      (5463, "zhanadgsan", 24), (43654, "lsisi", 25),(1, "zhangsan", 24), (2, "lsisi", 25)
    ))
      .toDF("id", "name", "age")
      .filter('id > 2)
    df.cache()
    print(df.rdd.getNumPartitions)

    val df1 = df.repartition(2)

    print(df1.rdd.getNumPartitions)
    val res = df1.groupBy('id,'name).agg(max('age))
    print(res.rdd.getNumPartitions)
    print(res.count())
    sparkSession.stop()

    /*
    * out: 1
    *      2
    *      200
    *      为什么是200?
    *      因为在sparksql中spark.sql.shuffle.partitions 默认是200,也就是默认会有200task同时执行
    *      shuffle操作,当然执行结束后肯定会变为200个分区了,注意shuffle write和shuffle read的并行度
    *      跟spark.default.parallelism设置的值无关,只跟spark.sql.shuffle.partitions设置的
    *      值有关。如果遇到数据倾斜或者shuffle太慢,可以适当增加这个值。
    *
    *      在对RDD执行shuffle算子时,需要给shuffle算子传入一个参数,比如reduceByKey(1000),
    *      该参数就设置了这个shuffle算子执行时shuffle read task的数量
    *
    *
    * */

/*解决数据倾斜最终办法:双重聚合(局部聚合+全局聚合)
(1) 方案适用场景
对RDD执行reduceByKey等聚合类shuffle算子或者在Spark SQL中使用group by语句进行分组聚合时,比较适用这种方案。

(2) 方案实现思路
这个方案的核心实现思路就是进行两阶段聚合。第一次是局部聚合,先给每个key都打上一个随机数,
比如10以内的随机数,此时原先一样的key就变成不一样的了,
比如(hello, 1) (hello, 1) (hello, 1) (hello, 1),
就会变成(1_hello, 1) (1_hello, 1) (2_hello, 1) (2_hello, 1)。
接着对打上随机数后的数据,执行reduceByKey等聚合操作,进行局部聚合,那么局部聚合结果
,就会变成了(1_hello, 2) (2_hello, 2)。
然后将各个key的前缀给去掉,就会变成(hello,2)(hello,2),
再次进行全局聚合操作,就可以得到最终结果了,比如(hello, 4)。

其他解决数据倾斜的方法:
https://cloud.tencent.com/developer/article/1336625

Spark性能调优06-JVM调优
https://cloud.tencent.com/developer/article/1336614

Spark性能调优05-Shuffle调优
https://cloud.tencent.com/developer/article/1336602

Spark性能调优03-数据本地化调优
https://cloud.tencent.com/developer/article/1336631
*/

  }

}

2.dologic2

package com.unisinsight.sparkDemo

import org.apache.spark.sql.SparkSession
import org.apache.spark.sql.functions._

/**
  * description
  * sparksql代码调优:
  * 1.大小表Join,将小表广播(自动或者手动),两个大表join时尽量在map阶段过滤掉不需要的数据
  * 2.优化序列化和反序列化的性能,Spark默认使用的是Java的序列化机制,也就是ObjectOutputStream/ObjectInputStream API
  * 来进行序列化和反序列化。但是Spark同时支持使用Kryo序列化库,Kryo序列化类库的性能比Java序
  * 列化类库的性能要高10倍左右
  * 3.使用reduceByKey等高级算子可以本地化预聚合,减轻shuffle I/O负担
  * 4.使用mapPartitions代替map,一次可以处理一个分区的数据,而不是1条,注意如果某个分区数据过多可能会导致oom。
  * 5.使用filter之后用coalesce减少分区个数,使用更少的task即可处理完所有的partition
  * 6.尽量减少repartition的使用,因为该算子会进行shuffle
  * 7.对要复用的rdd或者DF,persisit()或者cache()下来
  *
  * 性能和选择排序:MEMORY_ONLY>MEMORY_ONLY_SER>MEMORY_AND_DISK_SER>MEMORY_AND_DISK
  * MEMORY_ONLY_SER相比MEMORY_ONLY性能开销,主要就是序列化与反序列化的开销。
  * 使用MEMORY_AND_DISK_SER策略,而不是MEMORY_AND_DISK策略。因为既然到了这一步,
  * 就说明RDD的数据量很大,内存无法完全放下。序列化后的数据比较少,可以节省内存和磁盘的空间开销
  *
  *8.使用foreachPartitions替代foreach
  *9.选取启用合适的压缩算法snappy,在磁盘IO的确成为问题或者GC问题真的没有其它更好的解决办法的时候,可以考虑启用RDD压缩,否则没有必要。
  * @ author yuwei [yu.wei@unisinsight.com]
  * @ date 2018/12/26 14:31
  *
  * @since 1.0
  */
object Dologic2 {
  def main(args: Array[String]): Unit = {
    val sparkSession = SparkSession.builder
      .master("local[*]")
      .appName("appName")
      .config("spark.default.parallelism", 3)
      .config("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
      // 自动广播小表到每个执行器,在遇到join算子时自动执行map join而不执行reduce join
      // 从而不进行shuffle操作。
      // 被广播的表的大小需要小于spark.sql.autoBroadcastJoinThreshold所配置的值,,默认是10M
      //,通过将此设置为-1,广播可以被禁用。
      // 基表不能被广播,比如left outer join时,只能广播右表
      // 被广播的表目前只能是hive表,手动生成的DF不支持自动广播
      // 注意需要将driver的内存适当调大。
      .config("spark.sql.autoBroadcastJoinThreshold", 10 * 1024 * 1024)
      .config("spark.sql.join.preferSortMergeJoin", true)
      //选择spark.default.parallelism与local[*]中较小者作为初始并行度。
      // 如果是yarn提交则选择spark.default.parallelism作为初始并行度,因为该优先级高于yarn-submit
      .enableHiveSupport()
      .getOrCreate()
    import sparkSession.implicits._

    val df = sparkSession.createDataFrame(Seq((1, "zhangsan", 24), (2, "lsisi", 25),
      (54, "zhangewsan", 24), (44365, "lsisi", 25), (1, "zhangsan", 24), (2, "lsisi", 25),
      (5463, "zhanadgsan", 24), (44365, "lsisi", 25), (1, "zhangsan", 24), (2, "lsisi", 25)
    )).toDF("id", "name", "age")

    //等价于df.groupby('id).agg(sum('age))
    //但是reduceByKey可以本地化预聚合,可以减少节点间通信I/O,可节省shuffle时间
    //使用mapPartitions一次可以处理一个分区的数据,而不是1条,注意如果某个分区数据过多可能会导致oom
    //使用filter之后用coalesce减少分区个数,使用更少的task即可处理完所有的partition
    //   df.filter('id > 1).coalesce(1).rdd.mapPartitions(iterator => {
    //      val result = iterator.map(row => (row.getInt(0), row.getInt(2)))
    //      result
    //    }
    //    )
    //     .reduceByKey((x, y) => (x + y)).toDF().show(false)

    val df1 = sparkSession.createDataFrame(Seq((15, "zhangs2an", 24), (26, "ls1isi", 25),
      (54, "zhangewwsan", 24), (44365, "lsis2i", 25), (1, "zhansgqqsan", 24), (2, "lsiwsi", 25),
      (5463, "zhanadwsan", 24), (44365, "lswisi", 25), (1, "zhangsaan", 24), (2, "lsaisi", 25)
    )).toDF("id", "name", "num")


    // 手动实现小表broadcast,并且实现两表 Map-side-join
    val df2 = df.rdd.map(row => {
      (row.getInt(0), (row.getString(1), row.getInt(2)))
    }).collectAsMap()
    val listBroadCast = sparkSession.sparkContext.broadcast(df2)

    val df3 = df1.rdd.map(row => {
      (row.getInt(0), (row.getString(1), row.getInt(2)))
    }).mapPartitions(iterator => {
      val broadCastValue = listBroadCast.value

      // left join
      /*      val result = iterator.map(row => {
              val key = row._1
              val value = row._2
              if (broadCastValue.contains(key)) {
                (key.toInt, value._1.toString,value._2.toInt, broadCastValue.get(key).getOrElse(null)._1,broadCastValue.get(key).getOrElse(null)._2)
              }
              else
                (key.toInt, value._1.toString,value._2.toInt, null,0)
            })
            result*/

      // inner join
      for {
        (key, value) <- iterator
        // 返回满足if条件的(key,value)
        if (broadCastValue.contains(key))
      } yield (key.toInt, value._1.toString, value._2.toInt, broadCastValue.get(key).getOrElse(null)._1, broadCastValue.get(key).getOrElse(null)._2)

    })

    // df3.toDF().show(100, false)


    /*Reduce Side Join

      当两个文件/目录中的数据非常大,难以将某一个存放到内存中时,Reduce-side Join是一种解决思路。
      该算法需要通过Map和Reduce两个阶段完成,在Map阶段,将key相同的记录划分给同一个Reduce Task
      (需标记每条记录的来源,便于在Reduce阶段合并),在Reduce阶段,对key相同的进行合并。
      Spark提供了Join算子,可以直接通过该算子实现reduce-side join,但要求RDD中的记录必须是pair
      ,即RDD[KEY, VALUE],

      适用于两个join表数据量都很大的情况*/
    val dfr1 = df.rdd.map(row => {
      (row.getInt(0), (row.getString(1), row.getInt(2)))
    })

    val dfr2 = df1.rdd.map(row => {
      (row.getInt(0), (row.getString(1), row.getInt(2)))
    })
    // 设定的两表中的key要唯一(一般key由主键+记录时间构成),否则join会产生笛卡尔积
    val resDF = dfr1.join(dfr2).map(row => {

      (row._1, row._2._1._1, row._2._1._2, row._2._2._1, row._2._2._2)
    }).toDF().show(false)


    /*Map-side-join 与 Reduce Side Join 的比较:
    A、Map端join的好处是可以提前过滤掉join中需要排除的大量数据,会减少数据的传输。
    B、Reduce端做join是比较灵活,然后缺点是需要做大量数据传输、和整个shuffle过程都是耗时,
    因为知道reduce中才做join即对数据进行筛选。
    Reduce-side-join做join时, 尽量在map阶段过滤掉不需要的数据。
    Reduce-side-join做join时,要考虑能不能高效的再map端做join。
    */

/*序列化补充,在Spark中,主要有三个地方涉及到了序列化:

1.在算子函数中使用到外部变量时,该变量会被序列化后进行网络传输,比如广播变量
2.将自定义的类型作为RDD的泛型类型时(比如JavaRDD,Student是自定义类型),所有自定义类型对象,
都会进行序列化。因此这种情况下,也要求自定义的类必须实现Serializable接口。
3.使用可序列化的持久化策略时(比如MEMORY_ONLY_SER),Spark会将RDD中的每个partition都序
列化成一个大的字节数组。*/

/*相关参数:

spark.rdd.compress
参数说明:
 这个参数决定了RDD Cache的过程中,RDD数据在序列化之后是否进一步进行压缩再储存到内存或磁盘上,默认为false
参数设置建议:
 如果在磁盘IO的确成为问题或者GC问题真的没有其它更好的解决办法的时候,可以考虑启用RDD压缩,否则没有必要
spark.broadcast.compress
参数说明:
 是否对Broadcast的数据进行压缩,默认值为True
spark.io.compression.codec
参数说明:
 RDD Cache和Shuffle数据压缩所采用的算法,默认值曾经是使用LZF,但是因为LZF的内存开销的问题,默认的Codec已经改为Snappy*/



  }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spark调优参数是为了提高Spark应用程序的性能和效率。使用适当的参数可以优化任务的执行,提升Spark集群的吞吐量和响应时间。 一些常见的Spark调优参数包括: 1. spark.executor.memory:指定每个Executor的内存大小,默认为1g。可以根据任务的需求和集群的硬件配置来调整这个参数。 2. spark.executor.cores:指定每个Executor的核心数,默认为1。可以根据任务对CPU资源的需求来调整这个参数。 3. spark.driver.memory:指定Driver程序使用的内存大小,默认为1g。如果Driver程序运行较大的任务或需要处理大量数据,可以适当增加这个参数。 4. spark.default.parallelism:指定RDD默认的分区数,默认值为当前集群的可用核心数。根据数据量和计算资源来调整这个参数,以优化任务的并行度。 5. spark.shuffle.service.enabled:指定是否启用独立的Shuffle服务,默认为false。如果集群的Master节点性能较弱,建议启用该服务以减轻Master节点的压力。 6. spark.sql.shuffle.partitions:指定SQL查询中Shuffle操作的并行度,默认值为200。可以根据数据规模和硬件配置来调整这个参数,以提高Shuffle操作的效率。 7. spark.network.timeout:指定网络超时的时间,默认为120s。如果集群中有较慢的网络连接或任务需要处理大量数据,可以适当增加这个参数。 调优参数需要根据具体的任务和集群进行调整,通过合理配置这些参数可以提高Spark应用程序的性能和效率,加快数据处理的速度,减少任务的执行时间。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值