1.分配资源调优
在资源允许的条件下设置更多的executor数量和每个executor的内存大小。
当我们从客户端提交一个spark程序时 SparkContext、DAGScheduler、TaskScheduler会将程序中的算子,
切分成大量的task,提交到executor上面运行所以会增加executor数量和executor的CPU核数会增加了并行执行能力。
2.并行度调优
并行度是Spark中各个stage的task数量。
–>为啥设置并行度?
假如已经给spark作业分配了足够的资源,executor50个 内个内存10G 3个CPU,但是没有设置task,或者设置的很少。假如设置了100个task。
但是目前有150个CPU可以并行计算。平均每个executor分配两个task,造成了有一个CPU空闲。并行度没有和资源匹配,导致分配的资源浪费
–>合理设置并行度
task设置成总cup核数,但是总有运行快的task,先执行完的还是会有空余资源,官方推荐task设置成总CPU核数的2~3倍
3.重构RDD框架及RDD持久化 序列化
RDD重构和优化
在实际操作中我们应该避免出现一个RDD重复计算的情况,所以尽量复用RDD,
差不多的RDD可以抽取提供一个共同的RDD,后面的RDD计算重复使用就可以。
公共RDD实现持久化
多次使用的公共RDD实现持久化。
将RDD的数据缓存到内存/磁盘中,之后对RDD进行操作直接取这个RDD的持久化数据
持久化数据序列化
如果正常持久化还是会造成OOM(内存溢出),那就需要考虑序列化的方式在纯内存中存储(会将RDD的每个partition数据序列化成一个大的字节数组)
大大减少存储空间(但是唯一缺点就是读取数据时需要反序列化)
内存+磁盘 序列化
持久化+双副本机制(spark默认支持双副本机制)
为了数据的高可靠性,可以存储双双副本机制。如果出现数据丢失,不用重新计算,还可以使用另外一个副本,
这种方式也仅仅适用于内存极度充足的情况下
4.shuffle 调优
在spark中主要有以下几个算子
groupbykey:把分布在各个节点上的数据中的同一个key对应的values都集中在一块,集中到集群中的一个节点中,
也就是集中到一个节点的executor的一个task中。
reduceByKey:算子函数对values集合进行reduce操作,最后生成一个value。
5.广播变量(Broadcast)
性能的消耗
1.map会将副本通过网络传输到各个task中去 网络传输开销非常大
2.假如一个map1Mb 1000个map分布在1000个task中占用了1G内存不必要的内存消耗就会造成Rdd持久化到内存空间不足,
只能写入磁盘在磁盘io上也消耗性能
3.在创建task时发现堆内存放不下所有对象,会造成gc 平凡gc也会影响spark性能
广播变量的好处在于,不是每个task一份变量副本,而是变成每个executor一份,可以使变量生成的副本大大减少
task在运行的时候,想要使用广播变量中的数据,此时首先会在自己本地的Executor对应的BlockManager中,尝试获取变量副本;如果本地没 有,那么就从Driver远程拉取变量副本,并保存在本地的BlockManager中;此后这个executor上的task,都会直接使用本地的BlockManager中的 副本。executor的BlockManager除了从driver上拉取,也可能从其他节点的BlockManager上拉取变量副本,举例越近越好。
BlockMenaher是负责管理某个Executot对应的内存和磁盘上的数据
广播变量虽然起不到决定性的作用,配合其他调优但是一点一滴的积累也会达到不错的效果
6.kroy序列化的使用
问:Kryo序列化机制,一旦启用,会生效的几个地方?
1.算子函数中使用的外部变量(使用外部变量是需要序列化 )
2.持久化RDD时的序列化(优化内存占用和消耗 在task执行是创建对象时就不至于占满内存而频繁GC)
3.shuffle(优化网络传输的性能)
问:Spark 中如何使用序列化?
1.在SparkContext中设置一个属性
2.注册需要使用的kroy序列化的自定义类(如果要达到kryo最佳性能就一定要注册自定义类)
为什么kryo没有被作为默认的序列化类库?
应为kryo要求如果要达到最佳性能的话,就一定要注册你自定义的类 否则达不到最佳性能
默认情况下spark是使用java序列化的.好处在于比较方便,但是算子里面使用的变量必须是实现serializable接口的
但是缺点在于效率不高,速度比较慢,序列化以后的数据占用空间较大
可以手动更改序列化方式 kryo比java 速度快 序列化后的数据空间更小 大概是java的1/10
7.fastutil
fastutil 是扩展了java标准集合框架(Map,List,Set,HashMap,ArrayList,HashSet)的类库,
提供了特殊类型的Map,Set,List和queue,
fastutil集合类能够提供更小的内存占用,集合遍历,根据索引获取元素的值或设置元素的值时候更快的存取速度,
fastutil也提供了64位的array、set和list,以及高性能快速的,以及实用的IO类,来处理二进制和文本类型的文件;
fastutil适合于java7以上的版本
Spark中fastutil应用场景:
fastutil的每一种集合类型都实现了java中的标准接口(比如fastutil的map实现可java的Map接口)
还提供了java中没有的额外功能(比如双向迭代器)
1.如果算子函数使用了外部变量,那么可以用三步来优化:
a.使用Broadcast广播变量优化,
b.使用Kryo序列化类库优化,提升性能和效率,
c.如果外部变量是某种比较大的集合,可以使用fastutil改写外部变量
2.在算子函数中,如果要创建比较大的Map.List等集合,可以考虑将这些集合类型使用fastutil类库重写
在一定程度上减少task创建的集合类型的内存占用 避免executor内存频繁GC
8.JVM调优
Spark的JVM调优
存在的问题:降低cache(缓存)操作的内存占比
Spark Task执行算子函数会产生大量对象, 会被放入年轻代中,在年轻代内存比较小的时候,导致Eden和survivor区频繁内存溢满
导致频繁的minor GC。频繁的GC会导致一些存活的短声明的对象直接放入老年代。而老年代内存溢满,则会导致 Full GC
GC的时候 spark会停止工作等待GC结束。严重影响spark的速度
解决方案:增大内存
如何增大内存?
spark中,堆又分为两块。一块是专门用来给RDD的cache,persis操作进行缓存用的,
另一块就是给Spark算子用函数用的,存放函数中自己创建的对象。
我们可以通过查看每个stage中每个task运行的时间,GC时间等来判断是否发生了频繁的minorGC和fullGC(全局gc),从而来调低这个比例。
调节方法.
spark.storage.memoryFraction,0.6 -> 0.5 -> 0.4 -> 0.2
Java Stack(栈 也叫栈内存)
是java的运行区,在线程创建时创建。他的生命周期跟随线程的生命期,栈中不存在GC问题。线程结束栈也就释放了。
栈中的数据是一栈帧(stack frame)的格式存储的 栈帧是一个内存区块,是一个有关方法(method)和运行期数据的数据集。遵循"先进先出"原则
Java Heap (堆)
一个JVM实例中只有一个堆内存,大小可以调节的,也是JVM中用来存放对象与数组实例的地方。GC主要区域就这这里(GC还有可能在方法区)
类加载器读取文件之后,需要把类、方法、常变量放入堆内存中。以便执行器执行
问:堆内存分为几个部分?三个。
Permanent Space : 永久存储区
Young Generation Space 新生区 / New Generation
Tenure Generation Space 养老区 / Old Generation
jvm 新生代 老年代
java堆可以细分为新生代和老年代。
新生代:生命周期比较短的对象。(新生代与spark调优息息相关)
所有新生成的对象首先会放到新生代中,目标就是快速收集生命周期短的对象。
大部分对象在Eden区生成,当Eden区满时,还存活的对象将被复制到Survivor区中(两中的一个),当这个Survivor区满的时候,
此区存放的对象会被放在另一个Survivor区中,当另一个Survivor也满的时候,从第一个Survivor复制过来的还存活的对象将
被复制到老年代中,Survivor的两个区是对称的,没有先后关系,所以同一个Survivor取中可能存在从Eden复制过来的对象和从
另一个Survivor复制过来的对象,而且Survivor总有一个是空的,而且可以配置多余两个。
新生代常采用的算法:复制算法
每次使用Eden和其中一块Survivor,当回收时,将Eden和Survivor中还存活着的对象一次性地复制到另外一块Survivor空间上,
最后清理掉Eden和刚才用过的Survivor空间。当Eden空间、from Survivor和 to Survivor比例为(8:1:1)时,只有10%的内存被“浪费”。
老年代:生命周期比较长的对象。老年代常采用的算法:标记-清除算法和标记-整理算法
1.标记-清除算法
算法分为“标记”和“清除”两个阶段,首先标记出所有需要回收的对象,在标记完成后统一回收。
存在的问题:
(1)效率问题:标记和清除两个过程效率都不会高
(2)空间问题:标记清除后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中,
需要分配较大对象时,无法找打足够连续的内存而不得不提前触发另一次垃圾回收动作
2.标记-整理算法:
标记过程和标记-清理算法相同但是后续过程不是直接对可回收对象进行清理,
而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
总结:
在新生代,每次垃圾收集时都会有大量的对象死去,只有少量存活,那就选用复制算法,
只需要付出少量存活对象的复制成本可以完成收集。
在老年代,对象存活率高,没有额外的空间对他进行分配担保,
就必须只有标记-清除和标记-整理算法来完成回收。
spark调优的效果排名
1.分配资源,调节并行度,Rdd框架重构与缓存
2.shuffle调优
3.spark算子调优
4.Jvm调优,广播变量 keyo序列化 本地化等待时长 fastutil扩展类库
spark的优化方法
最新推荐文章于 2024-02-27 10:00:00 发布
710

被折叠的 条评论
为什么被折叠?



