Spark3性能调优(四)---AQE-DPP-Hint增强-故障排查问题

上文讲到Job优化,现在开始讲Spark3.0的新特性 AQE自适应查询执行DPPHint增强故障排查问题

前置信息

本文全部资源来源于《尚硅谷大数据技术之Spark3.x性能优化》和本人的学习感想,感兴趣的朋友可以去尚硅谷公众号获取资料学习。

Spark3.0 AQE自适应查询执行

Spark 在 3.0 版本推出了 AQE(Adaptive Query Execution),即自适应查询执行。AQE 是Spark SQL 的一种动态优化机制,在运行时,每当 Shuffle Map 阶段执行完毕,AQE 都会结合这个阶段的统计信息,基于既定的规则动态地调整、修正尚未执行的逻辑计划和物理计划,来完成对原始查询语句的运行时优化。

动态合并分区

在 Spark 中运行查询处理非常大的数据时,shuffle 通常会对查询性能产生非常重要的影响。shuffle 是非常昂贵的操作,因为它需要进行网络传输移动数据,以便下游进行计算。最好的分区取决于数据,但是每个查询的阶段之间的数据大小可能相差很大,这使得该数字难以调整:

  1. 如果分区太少,则每个分区的数据量可能会很大,处理这些数据量非常大的分区,可能需要将数据溢写到磁盘(例如,排序和聚合),降低了查询。
  2. 如果分区太多,则每个分区的数据量大小可能很小,读取大量小的网络数据块,这也会导致 I/O 效率低而降低了查询速度。拥有大量的 task(一个分区一个 task)也会给Spark 任务计划程序带来更多负担。

为了解决这个问题,我们可以在任务开始时先设置较多的 shuffle 分区个数,然后在运行时通过查看 shuffle 文件统计信息将相邻的小分区合并成更大的分区。

例如,假设正在运行 select max(i) from tbl group by j。输入 tbl 很小,在分组前只有 2个分区。那么任务刚初始化时,我们将分区数设置为 5,如果没有 AQE,Spark 将启动五个任务来进行最终聚合,但是其中会有三个非常小的分区,为每个分区启动单独的任务这样就很浪费。

在这里插入图片描述

在这里插入图片描述

取而代之的是,AQE 将这三个小分区合并为一个,因此最终聚只需三个 task 而不是五个。由图可以看到,task从Reduce2-Reduce4的小Task自动合并成一个大的Reduce2,可以减少资源的浪费了。

样例

def main( args: Array[String] ): Unit = {
  val sparkConf = new SparkConf().setAppName("AQEPartitionTunning")
    .set("spark.sql.autoBroadcastJoinThreshold", "-1") //为了演示效果,禁用广播join
    .set("spark.sql.adaptive.enabled", "true")
    .set("spark.sql.adaptive.coalescePartitions.enabled", "true") // 合并分区的开关
    .set("spark.sql.adaptive.coalescePartitions.initialPartitionNum","1000") // 初始的并行度
    .set("spark.sql.adaptive.coalescePartitions.minPartitionNum","10") // 合并后的最小分区数
    .set("spark.sql.adaptive.advisoryPartitionSizeInBytes", "20mb") // 合并后的分区,期望有多大
  val sparkSession: SparkSession = InitUtil.initSparkSession(sparkConf)
  useJoin(sparkSession)
}

def useJoin( sparkSession: SparkSession ) = {
  val saleCourse = sparkSession.sql("select *from sparktuning.sale_course")
  val coursePay = sparkSession.sql("select * from sparktuning.course_pay")
    .withColumnRenamed("discount", "pay_discount")
    .withColumnRenamed("createtime", "pay_createtime")
  val courseShoppingCart = sparkSession.sql("select *from sparktuning.course_shopping_cart")
    .drop("coursename")
    .withColumnRenamed("discount", "cart_discount")
    .withColumnRenamed("createtime", "cart_createtime")

  saleCourse.join(courseShoppingCart, Seq("courseid", "dt", "dn"), "right")
    .join(coursePay, Seq("orderid", "dt", "dn"), "left")
    .select("courseid", "coursename", "status", "pointlistid", "majorid", "chapterid", "chaptername", "edusubjectid"
      , "edusubjectname", "teacherid", "teachername", "coursemanager", "money", "orderid", "cart_discount", "sellmoney",
      "cart_createtime", "pay_discount", "paymoney", "pay_createtime", "dt", "dn")
    .write.mode(SaveMode.Overwrite).insertInto("sparktuning.salecourse_detail_1")
}
.set("spark.sql.autoBroadcastJoinThreshold", "-1") //为了演示效果,禁用广播join
.set("spark.sql.adaptive.enabled", "true")	// AQE总开关
.set("spark.sql.adaptive.coalescePartitions.enabled", "true") // 合并分区的开关
.set("spark.sql.adaptive.coalescePartitions.initialPartitionNum","1000") // 初始的并行度,这个值不去设置的话,默认就是200的
.set("spark.sql.adaptive.coalescePartitions.minPartitionNum","10") // 合并后的最小分区数!
.set("spark.sql.adaptive.advisoryPartitionSizeInBytes", "20mb") // 合并后的分区,期望有多大

动态切换Join策略

Spark 支持多种 join 策略,其中如果 join 的一张表可以很好的插入内存,那么broadcast shah join 通常性能最高。因此,spark join 中,如果小表小于广播大小阀值(默认10mb),Spark 将计划进行 broadcast hash join。但是,很多事情都会使这种大小估计出错(例如,存在选择性很高的过滤器),或者 join 关系是一系列的运算符而不是简单的扫描表操作。

为了解决此问题,AQE 现在根据最准确的 join 大小运行时重新计划 join 策略。从下图实例中可以看出,发现连接的右侧表比左侧表小的多,并且足够小可以进行广播,那么AQE 会重新优化,将 sort merge join 转换成为 broadcast hash join。

在这里插入图片描述

对于运行是的 broadcast hash join,可以将 shuffle 优化成本地 shuffle,优化掉 stage 减少网络传输。Broadcast hash join 可以规避 shuffle 阶段,相当于本地 join

def main( args: Array[String] ): Unit = {
  val sparkConf = new SparkConf().setAppName("AqeDynamicSwitchJoin")
    .set("spark.sql.adaptive.enabled", "true")
    .set("spark.sql.adaptive.localShuffleReader.enabled", "true") //在不需要进行shuffle重分区时,尝试使用本地shuffle读取器。将sort-meger join 转换为广播join
  val sparkSession: SparkSession = InitUtil.initSparkSession(sparkConf)
  switchJoinStartegies(sparkSession)
}


def switchJoinStartegies( sparkSession: SparkSession ) = {
  val coursePay = sparkSession.sql("select * from sparktuning.course_pay")
    .withColumnRenamed("discount", "pay_discount")
    .withColumnRenamed("createtime", "pay_createtime")
    .where("orderid between 'odid-9999000' and 'odid-9999999'")
  val courseShoppingCart = sparkSession.sql("select *from sparktuning.course_shopping_cart")
    .drop("coursename")
    .withColumnRenamed("discount", "cart_discount")
    .withColumnRenamed("createtime", "cart_createtime")
  val tmpdata = coursePay.join(courseShoppingCart, Seq("orderid"), "right")
  tmpdata.show()
}
.set("spark.sql.adaptive.enabled", "true")
.set("spark.sql.adaptive.localShuffleReader.enabled", "true") //在不需要进行shuffle重分区时,尝试使用本地shuffle读取器。将sort-meger join 转换为广播join

当这里打开时,就自适应的过滤掉一些信息,使程序看到 .where(“orderid between ‘odid-9999000’ and ‘odid-9999999’”),还发现可以走broadcast,那么就实现优化了。而没有打开时,还是会继续走SortMergeJosin,这样的性能会差很多的。

动态优化Join倾斜

当数据在群集中的分区之间分布不均匀时,就会发生数据倾斜。严重的倾斜会大大降低查询性能,尤其对于 join。AQE skew join 优化会从随机 shuffle 文件统计信息自动检测到这种倾斜。然后它将倾斜分区拆分成较小的子分区。

例如,下图 A join B,A 表中分区 A0 明细大于其他分区

在这里插入图片描述

没有这种优化,会导致其中一个分区特别耗时拖慢整个 stage,有了这个优化之后每个task 耗时都会大致相同,从而总体上获得更好的性能。

SPARK 3.0有了AQE机制就可以交给Spark自行解决。

  1. spark.sql.adaptive.skewJoin.enabled :是否开启倾斜 join 检测,如果开启了,那么会将倾斜的分区数据拆成多个分区,默认是开启的,但是得打开 aqe。
  2. spark.sql.adaptive.skewJoin.skewedPartitionFactor :默认值 5,此参数用来判断分区数据量是否数据倾斜,当任务中最大数据量分区对应的数据量大于的分区中位数乘以此参数,并且也大于spark.sql.adaptive.skewJoin.skewedPartitionThresholdInBytes 参数,那么此任务是数据倾斜。
  3. spark.sql.adaptive.skewJoin.skewedPartitionThresholdInBytes :默认值 256mb,用于判断是否数据倾斜
  4. spark.sql.adaptive.advisoryPartitionSizeInBytes :此参数用来告诉 spark 进行拆分后推荐分区大小是多少
  def main( args: Array[String] ): Unit = {
    val sparkConf = new SparkConf().setAppName("AqeOptimizingSkewJoin")
      .set("spark.sql.autoBroadcastJoinThreshold", "-1")  //为了演示效果,禁用广播join
      .set("spark.sql.adaptive.coalescePartitions.enabled", "false") // 为了演示效果,关闭自动缩小分区
      .set("spark.sql.adaptive.enabled", "true")
      .set("spark.sql.adaptive.skewJoin.enable","true")
      // 两个参数同时满足才打开执行划分操作
      .set("spark.sql.adaptive.skewJoin.skewedPartitionFactor","5")
      .set("spark.sql.adaptive.skewJoin.skewedPartitionThresholdInBytes","20mb")
      
      .set("spark.sql.adaptive.advisoryPartitionSizeInBytes", "8mb")
    val sparkSession: SparkSession = InitUtil.initSparkSession(sparkConf)
    useJoin(sparkSession)
  }

  def useJoin( sparkSession: SparkSession ) = {
    val saleCourse = sparkSession.sql("select *from sparktuning.sale_course")
    val coursePay = sparkSession.sql("select * from sparktuning.course_pay")
      .withColumnRenamed("discount", "pay_discount")
      .withColumnRenamed("createtime", "pay_createtime")
    val courseShoppingCart = sparkSession.sql("select *from sparktuning.course_shopping_cart")
      .drop("coursename")
      .withColumnRenamed("discount", "cart_discount")
      .withColumnRenamed("createtime", "cart_createtime")
    saleCourse.join(courseShoppingCart, Seq("courseid", "dt", "dn"), "right")
      .join(coursePay, Seq("orderid", "dt", "dn"), "left")
      .select("courseid", "coursename", "status", "pointlistid", "majorid", "chapterid", "chaptername", "edusubjectid"
        , "edusubjectname", "teacherid", "teachername", "coursemanager", "money", "orderid", "cart_discount", "sellmoney",
        "cart_createtime", "pay_discount", "paymoney", "pay_createtime", "dt", "dn")
      .write.mode(SaveMode.Overwrite).insertInto("sparktuning.salecourse_detail_1")
  }

在这里插入图片描述

原来SPARK WEB UI中的shuffle中有200个任务,而且有比较长的条。最大的Shuffle Read Size为28.7mb

在这里插入图片描述

当打开这个设置后,可以看到长的运行条被划分成4块了。且Shuffle Read Size为13.7mb。所以这里确实是有提升。

问题

.set("spark.sql.adaptive.coalescePartitions.enabled", "true")这里是会合并分区的,那这不就会跟这里的分割任务有矛盾吗?

答案:是如果同时开启了这个操作,那么先合并分区,再去判断倾斜

重新运行后,发现分区数减少了,但没有实现动态优化Join倾斜操作,这是因为合并分区之后,每 个分区数都变大了,每个分区大小×spark.sql.adaptive.skewJoin.skewedPartitionFactor","5"不满足动态优化Join倾斜操作的条件。

修改spark.sql.adaptive.skewJoin.skewedPartitionFactor","2"中位数的倍数为2后,重新执行。由于最大的task为28mb,远远大于中位数×2(14mb),就会发生打散分区的了啊。

Spark3.0 DPP

Spark3.0 支持动态分区裁剪 Dynamic Partition Pruning,简称 DPP,核心思路就是先将join 一侧作为子查询计算出来,再将其所有分区用到 join 另一侧作为表过滤条件,从而实现对分区的动态修剪,跟谓词下推有点关系。如下图所示

在这里插入图片描述

 select t1.id,t2.pkey from t1 join t2 on t1.pkey =t2.pkey and t2.id<2

优化成了

select t1.id,t2.pkey from t1 join t2 on t1.pkey=t2.pkey and t1.pkey in(select t2.pkey from t2 where t2.id<2)

触发条件

  1. 待裁剪的表 join 的时候,join 条件里必须有分区字段
  2. 如果是需要修剪左表,那么 join 必须是 inner join ,left semi join 或 right join,反之亦然。但如果是 left out join,无论右边有没有这个分区,左边的值都存在,就不需要被裁剪
  3. 另一张表需要存在至少一个过滤条件,比如 a join b on a.key=b.key and a.id<2参数 spark.sql.optimizer.dynamicPartitionPruning.enabled 默认开启。

Spark3.0 Hint增强

在 spark2.4 的时候就有了 hint 功能,不过只有 broadcast hash join 的 hint,这次 3.0 又增加了 sort merge join,shuffle_hash join,shuffle_replicate nested loop join

// TODO 广播Join的hint
sparkSession.sql("select /*+ BROADCAST(school) */ *  from test_student student left join test_school school on student.id=school.id").show()
sparkSession.sql("select /*+ BROADCASTJOIN(school) */ *  from test_student student left join test_school school on student.id=school.id").show()
sparkSession.sql("select /*+ MAPJOIN(school) */ *  from test_student student left join test_school school on student.id=school.id").show()

// TODO SortMergeJoin的hint
sparkSession.sql("select /*+ SHUFFLE_MERGE(school) */ *  from test_student student left join test_school school on student.id=school.id").show()
sparkSession.sql("select /*+ MERGEJOIN(school) */ *  from test_student student left join test_school school on student.id=school.id").show()
sparkSession.sql("select /*+ MERGE(school) */ *  from test_student student left join test_school school on student.id=school.id").show()

// TODO shuffle hash join 的hint
sparkSession.sql("select /*+ SHUFFLE_HASH(school) */ *  from test_student student left join test_school school on student.id=school.id").show()

// TODO SHUFFLE_REPLICATE_NL join 的hint
sparkSession.sql("select /*+ SHUFFLE_REPLICATE_NL(school) */ *  from test_student student inner join test_school school on student.id=school.id").show()

故障排除

控制reduce端缓冲大小以避免OOM

在 Shuffle 过程,reduce 端 task 并不是等到 map 端 task 将其数据全部写入磁盘后再去拉取,而是 map 端写一点数据,reduce 端 task 就会拉取一小部分数据,然后立即进行后面的聚合、算子函数的使用等操作。

reduce 端 task 能够拉取多少数据,由 reduce 拉取数据的缓冲区 buffer 来决定,因为拉取过来的数据都是先放在 buffer 中,然后再进行后续的处理,buffer 的默认大小为 48MB。

reduce 端 task 会一边拉取一边计算,不一定每次都会拉满 48MB 的数据,可能大多数时候拉取一部分数据就处理掉了。

虽然说增大 reduce 端缓冲区大小可以减少拉取次数,提升 Shuffle 性能,但是有时map 端的数据量非常大,写出的速度非常快,此时 reduce 端的所有 task 在拉取的时候,有可能全部达到自己缓冲的最大极限值,即 48MB,此时,再加上 reduce 端执行的聚合函数的代码,可能会创建大量的对象,这可难会导致内存溢出,即 OOM。

如果一旦出现 reduce 端内存溢出的问题,我们可以考虑减小 reduce 端拉取数据缓冲区的大小,例如减少为 12MB。

在实际生产环境中是出现过这种问题的,这是典型的以性能换执行的原理。reduce 端拉取数据的缓冲区减小,不容易导致 OOM,但是相应的,reudce 端的拉取次数增加,造成更多的网络传输开销,造成性能的下降。注意,要保证任务能够运行,再考虑性能的优化。

JVM GC 导致的 shuffle 文件拉取失败

在上一章Job的优化也讲过一下

在 Spark 作业中,有时会出现 shuffle file not found 的错误,这是非常常见的一个报错,有时出现这种错误以后,选择重新执行一遍,就不再报出这种错误。

出现上述问题可能的原因是 Shuffle 操作中,后面 stage 的 task 想要去上一个 stage 的task 所在的 Executor 拉取数据,结果对方正在执行 GC,执行 GC 会导致 Executor 内所有的工作现场全部停止,比如 BlockManager、基于 netty 的网络通信等,这就会导致后面的task 拉取数据拉取了半天都没有拉取到,就会报出 shuffle file not found 的错误,而第二次再次执行就不会再出现这种错误。

可以通过调整 reduce 端拉取数据重试次数和 reduce 端拉取数据时间间隔这两个参数来对 Shuffle 性能进行调整,增大参数值,使得 reduce 端拉取数据的重试次数增加,并且每次失败后等待的时间间隔加长。

解决各种序列化导致的报错

如果看到报错中有Serializable等字眼,就可能是序列化错误。

序列化问题注意

  1. 作为 RDD 的元素类型的自定义类,必须是可以序列化的;
  2. 算子函数里可以使用的外部的自定义变量,必须是可以序列化的;
  3. 不可以在 RDD 的元素类型、算子函数里使用第三方的不支持序列化的类型,例如
    Connection。

解决算子函数返回 NULL 导致的问题

在一些算子函数里,需要我们有一个返回值,但是在一些情况下我们不希望有返回值,此时我们如果直接返回 NULL,会报错,例如 Scala.Math(NULL)异常。

解决办法:

  1. 返回特殊值,不返回 NULL,例如“-1”
  2. 在通过算子获取到了一个 RDD 之后,可以对这个 RDD 执行 filter 操作,进行数据过滤,将数值为-1 的数据给过滤掉
  3. 在使用完 filter 算子后,继续调用 coalesce 算子进行优化

解决 YARN-CLIENT 模式导致的网卡流量激增问题

这个问题主要出现在client模式下

如果是Executor多,task也多,那么Driver就要频繁的跟task通信,使造成频繁的网络通讯。

这个时候使用cluster模式就可以了。

解决 YARN-CLUSTER 模式的 JVM 栈内存溢出无法执行问题

当 Spark 作业中包含 SparkSQL 的内容时,可能会碰到 YARN-client 模式下可以运行,但是 YARN-cluster 模式下无法提交运行(报出 OOM 错误)的情况。

YARN-client 模式下,Driver 是运行在本地机器上的,Spark 使用的 JVM 的 PermGen 的配置,是本地机器上的 spark-class 文件,JVM 永久代的大小是 128MB,这个是没有问题的,但是在 YARN-cluster 模式下,Driver 运行在 YARN 集群的某个节点上,使用的是没有经过配置的默认设置,PermGen 永久代大小为 82MB

SparkSQL 的内部要进行很复杂的 SQL 的语义解析、语法树转换等等,非常复杂,如果sql 语句本身就非常复杂,那么很有可能会导致性能的损耗和内存的占用,特别是对PermGen 的占用会比较大。

所以,此时如果 PermGen 的占用好过了 82MB,但是又小于 128MB,就会出现 YARN-client 模式下可以运行,YARN-cluster 模式下无法运行的情况。解决上述问题的方法时增加 PermGen 的容量,需要在 spark-submit 脚本中对相关参数进行设置,设置方法如代码清单所示。
--conf spark.driver.extraJavaOptions="-XX:PermSize=128M -XX:MaxPermSize=256M"

通过上述方法就设置了 Driver 永久代的大小,默认为 128MB,最大 256MB,这样就可
以避免上面所说的问题

解决 SparkSQL 导致的 JVM 栈内存溢出

当 SparkSQL 的 sql 语句有成百上千的 or 关键字时,就可能会出现 Driver 端的 JVM 栈内存溢出。

JVM 栈内存溢出基本上就是由于调用的方法层级过多,产生了大量的,非常深的,超出了 JVM 栈深度限制的递归。(我们猜测 SparkSQL 有大量 or 语句的时候,在解析 SQL 时,例如转换为语法树或者进行执行计划的生成的时候,对于 or 的处理是递归,or 非常多时,会发生大量的递归)

此时,建议将一条 sql 语句拆分为多条 sql 语句来执行,每条 sql 语句尽量保证 100 个以内的子句。根据实际的生产环境试验,一条 sql 语句的 or 关键字控制在 100 个以内,通常不会导致 JVM 栈内存溢出。

持久化与 checkpoint 的使用

使用checkpoint提高Spark 作业的可靠性,一旦缓存出现问题,不需要重新计算数据,缺点在于,checkpoint 时需要将数据写入 HDFS 等文件系统,对性能的消耗较大

内存泄漏排查

内存泄露是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢,甚至系统崩溃等严重后果。

在 Spark Streaming 中往往会因为开发者代码未正确编写导致无法回收或释放对象,造成 Spark Streaming 内存泄露越跑越慢甚至崩溃的结果。那么排查内存泄露需要一些第三方的工具

利用IBM HeapAnalyzer工具可以排查一下内存泄漏排查问题

频繁GC问题

  1. 打印 GC 详情统计一下 GC 启动的频率和 GC 使用的总时间,在 spark-submit 提交的时候设置参数--conf "spark.executor.extraJavaOptions=-XX:+PrintGCDetails -XX:+PrintGCTimeStamps"

    如果出现了多次 Full GC,首先考虑的是可能配置的 Executor 内存较低,这个时候需要
    增加 Executor Memory 来调节

  2. 如果一个任务结束前,Full GC 执行多次,说明老年代空间被占满了,那么有可能是没有分配足够的内存。

    1. 调整 executor 的内存,配置参数 executor-memory
    2. 调整老年代所占比例:配置-XX:NewRatio 的比例值
    3. 降低 park.memory.storageFraction 减少用于缓存的空间
  3. 如果有太多Minor GC,但是 Full GC 不多,可以给 Eden 分配更多的内存

    1. 比如 Eden 代的内存需求量为 E,可以设置 Young 代的内存为-Xmn=4/3*E,设置该值也会导致Survivor 区域扩张
    2. 调整 Eden 在年轻代所占的比例,配置-XX:SurvivorRatio 的比例值
  4. 调整垃圾回收器,通常使用 G1GC,即配置-XX:+UseG1GC。当 Executor 的堆空间比较大时,可以提升G1 region size(-XX:G1HeapRegionSize),在提交参数指定:--conf "spark.executor.extraJavaOptions=-XX:+UseG1GC -XX:G1HeapRegionSize=16M -XX:+PrintGCDetails -XX:+PrintGCTimeStamps"

小结

这节课我们学习了Spark3.0的AQE、DPP、Hint特性,还学习了一些故障排查问题。

总而研制,学习内容可以用以下信息来概括:

Spark3.0 AQE自适应查询执行

  1. 动态合并分区:打开这个配置,可以自适应地将小分区和并在一起,从而减少小文件数
  2. 动态切换Join策略:打开了这个策略。AQE 现在根据最准确的 join 大小运行时重新计划 join 策略。比如说连接的右侧表比左侧表小的多,并且足够小可以进行广播,那么AQE 会重新优化,将 sort merge join 转换成为 broadcast hash join。
  3. 动态优化Join倾斜:AQE skew join 优化会从随机 shuffle 文件统计信息自动检测到这种倾斜。然后它将倾斜分区拆分成较小的子分区。当同时打开合并分区配置和优化Join倾斜配置后,将首先合并小文件分区,再执行优化Join倾斜,从而实现性能优化的效果。

Spark3.0 DPP:Spark3.0 支持动态分区裁剪 Dynamic Partition Pruning,简称 DPP,核心思路就是先将join 一侧作为子查询计算出来,再将其所有分区用到 join 另一侧作为表过滤条件,从而实现对分区的动态修剪,就是谓词下推的了啊。

Spark3.0 Hint增强:Hint增强包括sort merge joinshuffle_hash joinshuffle_replicate nested loop join

故障排除:学习了几个故障排查信息

  1. reduce端缓冲大小不要调太大,防止OOM
  2. 当GC导致shuffle文件拉取失败时,可以调高拉取次数和等待间隔
  3. 算子返回null时,修改成特殊值,然后再过滤,再coaleasce以减少分区数
  4. yarn-client模式下会有网卡流量激增的问题,这个时候可以改用cluster就可以解决
  5. yarn-cluster的永久代(元信息)过大,导致溢出问题,调大它即可
  6. SparkSQL导致的JVM栈内存溢出,跟or操作有关系,or太多了就导致JVM栈内存溢出,这个时候可以拆分SQL以减少栈JVM栈内存溢出的问题
  7. 必要时利用checkpoint,以减少故障产生时重试带来的时间浪费
  8. 内存溢出排查
  9. 频繁GC的时候,如果是多次Full GC,可能是老年代的问题,提高老年的比例或者内存大小;太多minor GC,调整年轻代的Eden区域大小。调整垃圾回收器,通常使用 G1GC,即配置-XX:+UseG1GC。当 Executor 的堆空间比较大时,可以提升 G1 region size(-XX:G1HeapRegionSize)

看到这里,也算是学完了尚硅谷Spark调优的课程了,但学习的深度还不深,实操得也不多了,有些原理其实也弄得不太清楚,接下来还得努力深造啊!

感谢各位读者读到这里,感谢大家!!!!!!!!!

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值