spark

19次阅读
没有评论

今天就跟大家聊聊有关 spark-JVM 的调优原理是什么,可能很多人都不太了解,为了让大家更加了解,丸趣 TV 小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。

性能调优

常规性能调优:分配资源、并行度。。。等

JVM 调优(Java 虚拟机):JVM 相关的参数,通常情况下,如果你的硬件配置、基础的 JVM 的配置,都可以的话,JVM 通常不会造成太严重的性能问题;反而更多的是,在 troubleshooting 中,JVM 占了很重要的地位;JVM 造成线上的 spark 作业的运行报错,甚至失败(比如 OOM)。

shuffle 调优(相当重要):spark 在执行 groupByKey、reduceByKey 等操作时的,shuffle 环节的调优。这个很重要。shuffle 调优,其实对 spark 作业的性能的影响,是相当之高!!!经验:在 spark 作业的运行过程中,只要一牵扯到有 shuffle 的操作,基本上 shuffle 操作的性能消耗,要占到整个 spark 作业的 50%~90%。10% 用来运行 map 等操作,90% 耗费在 shuffle 操作。

spark 操作调优(spark 算子调优,比较重要):有些算子的性能,是比其他一些算子的性能要高的。foreachPartition 替代 foreach。

如果一旦遇到合适的情况,效果还是不错的。

1、分配资源、并行度、RDD 架构与缓存
2、shuffle 调优
3、spark 算子调优
4、JVM 调优、广播大变量。。。

JVM 调优原理概述。

JVM 调优里面所有官方都推荐来降低 cache 操作占比

理论基础:spark 是用 scala 开发的。大家不要以为 scala 就跟 java 一点关系都没有了,这是一个很常见的错误。spark 的 scala 代码调用了很多 java api。scala 也是运行在 java 虚拟机中的。spark 是运行在 java 虚拟机中的。java 虚拟机可能会产生什么样的问题:内存不足??!!我们的 RDD 的缓存、task 运行定义的算子函数,可能会创建很多对象。都可能会占用大量内存,没搞好的话,可能导致 JVM 出问题。

堆内存:

存放我们创建的一些对象,堆内存分为年轻带 young generation 和老年带 old generation,年轻带内部又分为三块,Eden 区域比较大,两个 survivor 区域比较小存活区域我们在 spark task 执行算子函数(我们自己写的针对 RDD 的操作),可能会创建很多对象,这些对象,都是要放入 JVM 年轻代中的。每一次放对象的时候,都是放入 eden 区域,和其中一个 survivor 区域;另外一个 survivor 区域是空闲的。当 eden 区域和一个 survivor 区域放满了以后(spark 运行过程中,产生的对象实在太多了),就会触发 minor gc,小型垃圾回收。垃圾回收器 gc 会把不再使用的对象,从内存中清空,给后面新创建的对象腾出来点儿地方。

清理掉了不再使用的对象之后,那么也会将存活下来的对象(还要继续使用的),放入之前空闲的那一个 survivor 区域中。这里可能会出现一个问题。默认 eden、survior1 和 survivor2 的内存占比是 8:1:1。问题是,如果存活下来的对象是 1.5,一个 survivor 区域放不下。此时就可能通过 JVM 的担保机制(不同 JVM 版本可能对应的行为),将多余的对象,直接放入老年代了。

如果你的 JVM 内存不够大的话,可能导致频繁的年轻代内存满溢,频繁的进行 minor gc。频繁的 minor gc 会导致短时间内,有些存活的对象,多次垃圾回收都没有回收掉。就是那些一直在用的又不能被释放的就频繁的倒来倒去!会导致这种短声明周期(其实不一定是要长期使用的)对象,每回收一次,年龄长一岁!年龄过大,垃圾回收次数太多还没有回收到,跑到老年代。

说白了就是短声明周期对象却跑到老年代里面去了!!!本来是短周期的,结果倒来倒去跑到老年代里面去了,理想情况下,老年代都是放一些生命周期很长的对象,数量应该是很少的。比如数据库连接池,数据库连接池本来就很少。

简而言之,老年代中,可能会因为内存不足,囤积一大堆,短生命周期的,本来应该在年轻代中的,可能马上就要被回收掉的对象。此时,可能导致老年代频繁满溢。频繁进行 full gc(全局 / 全面垃圾回收)。full gc 就会去回收老年代中的对象。full gc 由于这个算法的设计,是针对的是,老年代中的对象数量很少,满溢进行 full gc 的频率应该很少,因此采取了不太复杂,但是耗费性能和时间的垃圾回收算法。full gc 很慢。

full gc / minor gc,无论是快,还是慢,都会导致 jvm 的工作线程停止工作,stop the world。简而言之,就是说,gc 的时候,spark 停止工作了。等着垃圾回收结束。

内存不充足的时候,问题:

频繁 minor gc,也会导致频繁 spark 停止工作;

老年代囤积大量活跃对象(短生命周期的对象),导致频繁 full gc,full gc 时间很长,短则数十秒,长则数分钟,甚至数小时。可能导致 spark 长时间停止工作;

严重影响咱们的 spark 的性能和运行的速度。

如何解决?

JVM 调优的第一个点:降低 cache 操作的内存占比

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

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

针对上述这种情况,大家可以在 spark uich 查看。yarn 去运行的话,那么就通过 yarn 的界面,去查看你的 spark 作业的运行统计,很简单,大家一层一层点击进去就好。可以看到每个 stage 的运行情况,包括每个 task 的运行时间、gc 时间等等。如果发现 gc 太频繁,时间太长。此时就可以适当调节这个比例。降低 cache 操作的内存占比,大不了用 persist 操作,选择将一部分缓存的 RDD 数据写入磁盘,或者序列化方式,配合 Kryo 序列化类,减少 RDD 缓存的内存占用;降低 cache 操作内存占比;对应的,算子函数的内存占比就提升了。这个时候,可能,就可以减少 minor gc 的频率,同时减少 full gc 的频率。对性能的提升是有一定的帮助的。一句话,让 task 执行算子函数时,有更多的内存可以使用。

spark.storage.memoryFraction,0.6 -  0.5 -  0.4 -  0.2

大家可以自己去调,然后观察 spark 作业的运行统计!!!然后看看整体运行时间有没有改善!gc 是否频繁,gc 时间等!上述比例都可以调!根据不同需求来做!

.set(spark.storage.memoryFraction ,  0.5)

看完上述内容,你们对 spark-JVM 的调优原理是什么有进一步的了解吗?如果还想了解更多知识或者相关内容,请关注丸趣 TV 行业资讯频道,感谢大家的支持。